diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 14:07:19 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 14:07:58 +0300 |
| commit | 04616d5cff135a3f2fa0dcd99c3d756b67312daa (patch) | |
| tree | 41507222a4a265acbe3f546cbe731d729e2a34d1 | |
| parent | 0eec911bdabb130415aeb2fc6eb4281b891a5d2f (diff) | |
Test pre-init public API, header portability and integration requests
Add tests/common/test_public_api.c: before yasl_init, argument errors are reported first, owner-thread calls fail with ENODEV, queries return their documented fallbacks, yasl_drop_next_path parses a crafted text/uri-list, yasl_get_system_string follows its length and NUL rules, and yasl_wake is a no-op with no display.
Build tests/test_header_c11.c as strict C11 and C17 with -pedantic-errors.
Extend the surface integration test, run on weston and Xwayland, with title, window requests, bell, desktop queries, drag cancel, input-less move/menu/drag calls, a popup (its parent's destroy must also remove it, checked through the display's surface list), and a clipboard round trip.
Run tests/x11/test_input.c in the X11 phase of run_surfaces.py through an optional trailing argument.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
| -rw-r--r-- | tests/common/test_public_api.c | 131 | ||||
| -rw-r--r-- | tests/common/test_surface_integration.c | 77 | ||||
| -rw-r--r-- | tests/helpers/run_surfaces.py | 10 | ||||
| -rw-r--r-- | tests/meson.build | 16 | ||||
| -rw-r--r-- | tests/test_header_c11.c | 11 |
5 files changed, 240 insertions, 5 deletions
diff --git a/tests/common/test_public_api.c b/tests/common/test_public_api.c new file mode 100644 index 0000000..a53a8ec --- /dev/null +++ b/tests/common/test_public_api.c @@ -0,0 +1,131 @@ +#include <yasl/yasl.h> +#include <assert.h> +#include <string.h> + +/* Before yasl_init there is no display: argument errors are reported first, owner-thread + * calls fail with ENODEV, and queries return their documented fallbacks. Nothing here may + * open a display, so only invalid arguments reach the constructors and openers. */ +int main(void) +{ + static char anchor; /* A fake handle; it is compared and never dereferenced. */ + yasl_surface *fake = (yasl_surface *)&anchor; + + assert(yasl_shutdown() == 0 && yasl_shutdown() == 0); + assert(yasl_get_backend() == YASL_BACKEND_AUTO && yasl_get_features() == 0); + assert(yasl_get_appearance() == 0 && yasl_get_double_click_ms() == 400); + + /* Invalid constructor arguments fail before any display is opened. */ + assert(!yasl_surface_create(&(struct yasl_surface_options){.width = 32767, .height = 32767}) && + errno == EINVAL); + assert(!yasl_surface_create(&(struct yasl_surface_options){.width = 32768, .height = 1}) && + errno == EINVAL); + assert(!yasl_surface_create(&(struct yasl_surface_options){ + .width = 8, .height = 8, .type = YASL_SURFACE_GL, .gl = {.share = fake}}) && + errno == EINVAL); + + /* Owner-thread calls need a display, whatever the surface argument is. */ + assert(yasl_surface_destroy(NULL) == 0 && yasl_surface_destroy(fake) == -ENODEV); + assert(yasl_surface_set_userdata(NULL, &anchor) == -ENODEV); + assert(yasl_surface_set_title(NULL, "x") == -ENODEV); + assert(yasl_surface_set_title(fake, NULL) == -ENODEV); + assert(yasl_surface_request(NULL, YASL_REQUEST_MAXIMIZE) == -ENODEV); + assert(yasl_surface_request(fake, (enum yasl_request)9) == -ENODEV); + assert(yasl_surface_request_size(NULL, 10, 10) == -ENODEV); + assert(yasl_surface_set_cursor(NULL, YASL_CURSOR_ARROW) == -ENODEV); + assert(yasl_surface_set_text_input(NULL, NULL) == -ENODEV); + assert(yasl_surface_begin_move_resize(NULL, 0) == -ENODEV); + assert(yasl_surface_show_window_menu(NULL, 1, 1) == -ENODEV); + assert(yasl_surface_bell(NULL) == -ENODEV); + assert(yasl_surface_place_under_pointer(NULL, 1, 1) == -ENODEV); + assert(yasl_drag_carry(NULL, 1, 1) == -ENODEV); + assert(yasl_drag_start(NULL, NULL) == -ENODEV); + assert(yasl_drag_cancel() == -ENODEV); + assert(yasl_clipboard_set(YASL_CLIPBOARD, "x") == -ENODEV); + assert(yasl_clipboard_set(YASL_PRIMARY, NULL) == -ENODEV); + assert(yasl_clipboard_request(NULL, YASL_CLIPBOARD) == -ENODEV); + assert(yasl_notify("title", NULL) == -ENODEV); + assert(yasl_cpu_present(NULL) == -ENODEV); + assert(yasl_gl_set_swap_interval(NULL, 1) == -ENODEV); + assert(yasl_gl_present(NULL) == -ENODEV); + assert(yasl_gl_make_current(fake) == -ENODEV); + assert(!yasl_gl_get_proc_address("glClear") && errno == ENODEV); + assert(!yasl_gl_get_proc_address(NULL) && errno == ENODEV); + assert(!yasl_metal_get_layer(NULL) && errno == ENODEV); + struct yasl_popup_options popup = {.width = 4, .height = 4}; + assert(yasl_popup_reposition(NULL, &popup) == -ENODEV); + assert(!yasl_popup_create(NULL, &popup) && errno == ENODEV); + + /* Queries name their fallbacks. */ + assert(yasl_surface_get_userdata(NULL) == NULL && yasl_surface_get_userdata(fake) == NULL); + struct yasl_surface_info info = yasl_surface_get_info(NULL); + assert(info.width == 0 && info.height == 0 && info.pixel_width == 0 && info.pixel_height == 0 && + info.scale == 1.0 && info.state == 0); + info = yasl_surface_get_info(fake); + assert(info.width == 0 && info.pixel_height == 0 && info.scale == 1.0); + int w = 7, h = 7; + assert(!yasl_cpu_pixels(NULL, &w, &h) && errno == ENODEV && w == 0 && h == 0); + assert(!yasl_cpu_pixels(NULL, NULL, NULL) && errno == ENODEV); + struct yasl_native native = {.x11_window = 7}; + assert(yasl_surface_get_native(NULL, NULL) == -EINVAL); + assert(yasl_surface_get_native(NULL, &native) == -ENODEV && native.x11_window == 0); + + /* Waiting needs a display; yasl_wake with no display does nothing and does not crash. */ + yasl_event e = {.type = YASL_EVENT_QUIT}; + assert(yasl_wait_event(&e, 0) == -ENODEV && e.type == YASL_EVENT_NONE); + yasl_wake(); + yasl_wake(); + assert(yasl_shutdown() == 0); + + /* The event clock never runs backwards. */ + uint64_t last = yasl_time_ns(); + assert(last > 0); + for (int i = 0; i < 1000; i++) { + uint64_t now = yasl_time_ns(); + assert(now >= last); + last = now; + } + + /* yasl_open_uri rejects bad URIs without opening anything. */ + assert(yasl_open_uri(NULL) == -EINVAL); + assert(yasl_open_uri("") == -EINVAL); + assert(yasl_open_uri("no-colon") == -EINVAL); + assert(yasl_open_uri("1http://x") == -EINVAL); + assert(yasl_open_uri("http://x\n") == -EINVAL); + + /* yasl_drop_next_path walks a text/uri-list: local file lines only, comments skipped. */ + static const char uris[] = + "file:///a/b\r\nhttp://x/y\r\n# comment\r\nfile://localhost/c%20d\r\n"; + const char *cursor = uris; + char out[64] = "keep"; + assert(yasl_drop_next_path(&cursor, NULL, 0) == 4 && cursor == uris); + assert(yasl_drop_next_path(&cursor, out, 4) == 4 && cursor == uris && !strcmp(out, "keep")); + assert(yasl_drop_next_path(&cursor, out, 5) == 4 && cursor != uris && !strcmp(out, "/a/b")); + assert(yasl_drop_next_path(&cursor, out, sizeof out) == 4 && !strcmp(out, "/c d")); + assert(yasl_drop_next_path(&cursor, out, sizeof out) == 0); + assert(yasl_drop_next_path(NULL, out, sizeof out) == 0); + const char *fresh = uris; + assert(yasl_drop_next_path(&fresh, NULL, 1) == 0 && fresh == uris); + + /* yasl_get_system_string: length first, the NUL only when the capacity exceeds it. */ + char path[4096], full[4096], locale[64]; + size_t n = yasl_get_system_string(YASL_EXECUTABLE_PATH, NULL, 0); + assert(n > 0 && n < sizeof full); + assert(yasl_get_system_string(YASL_EXECUTABLE_PATH, full, sizeof full) == n && strlen(full) == n); + memset(path, 'x', sizeof path); + assert(yasl_get_system_string(YASL_EXECUTABLE_PATH, path, n) == n && path[n - 1] == 'x' && + path[n] == 'x'); + assert(yasl_get_system_string(YASL_EXECUTABLE_PATH, path, n - 1) == n && path[n - 2] == 'x' && + path[n] == 'x'); + assert(yasl_get_system_string(YASL_EXECUTABLE_PATH, path, n + 1) == n && path[n] == '\0' && + !strcmp(path, full)); + size_t ln = yasl_get_system_string(YASL_LOCALE, locale, sizeof locale); + assert(ln > 0 && ln < sizeof locale && strlen(locale) == ln && !strpbrk(locale, ".@")); + assert(yasl_get_system_string(YASL_LOCALE, NULL, 16) == 0 && errno == EINVAL); + assert(yasl_get_system_string((enum yasl_system_string)9, locale, sizeof locale) == 0 && + errno == EINVAL); +#if defined(__linux__) + errno = 0; + assert(yasl_get_system_string(YASL_RESOURCE_DIR, path, sizeof path) == 0 && errno == ENOTSUP); +#endif + return 0; +} diff --git a/tests/common/test_surface_integration.c b/tests/common/test_surface_integration.c index 072a307..9067640 100644 --- a/tests/common/test_surface_integration.c +++ b/tests/common/test_surface_integration.c @@ -40,6 +40,14 @@ static void push(yasl_surface *s, struct yasl_private_event event) assert(yasl_private_events_push(s->display, s, &event)); } static int refreshes; +/* Whether the display still lists the surface with this address; compared, never dereferenced. */ +static bool listed(struct yasl_private_display *d, uintptr_t id) +{ + for (yasl_surface *p = d->surfaces; p; p = p->event_next) + if ((uintptr_t)p == id) + return true; + return false; +} static void refresh(yasl_surface *s) { refreshes++; @@ -244,7 +252,74 @@ int main(int argc, char **argv) s->display->modal = false; #endif s->refresh = NULL; - yasl_surface_destroy(s); + + /* Title and window requests: each either works or reports ENOTSUP. */ + assert(yasl_surface_set_title(s, "CPU contract renamed") == 0); + static const enum yasl_request requests[] = {YASL_REQUEST_RESTORE, YASL_REQUEST_FULLSCREEN, + YASL_REQUEST_MAXIMIZE, YASL_REQUEST_MINIMIZE}; + for (size_t i = 0; i < sizeof requests / sizeof requests[0]; i++) { + int r = yasl_surface_request(s, requests[i]); + assert(r == 0 || r == -ENOTSUP); + } + drain(); + int restored = yasl_surface_request(s, YASL_REQUEST_RESTORE); + assert(restored == 0 || restored == -ENOTSUP); + drain(); + /* ENOTSUP is the documented answer when the compositor lacks xdg-system-bell; Wayland + * currently reports EIO there, so only the 0-or-errno rule is checked. */ + assert(yasl_surface_bell(s) <= 0); + + /* Desktop queries and the drag cancel, which is a no-op without a drag. */ + assert(yasl_get_appearance() <= 7); + int double_click = yasl_get_double_click_ms(); + assert(double_click >= 1 && double_click <= 10000); + assert(yasl_drag_cancel() == 0); + + /* Without a pointer press these may fail with an errno; they must simply return. */ + assert(yasl_surface_begin_move_resize(s, 0) <= 0); + assert(yasl_surface_show_window_menu(s, 1, 1) <= 0); + assert(yasl_drag_start(s, NULL) <= 0); + drain(); + + /* A popup needs an input grab to open; if it opens, its parent owns it. */ + struct yasl_private_display *display = s->display; + struct yasl_popup_options popup_options = { + .width = 40, .height = 30, .anchor_width = 1, .anchor_height = 1}; + errno = 0; + yasl_surface *popup = yasl_popup_create(s, &popup_options); + uintptr_t popup_id = (uintptr_t)popup; + if (!popup) { + assert(errno != 0); + } else { + assert(listed(display, popup_id)); + assert(yasl_popup_reposition(popup, &popup_options) <= 0); + int pw = 0, ph = 0; + assert(yasl_cpu_pixels(popup, &pw, &ph) && pw > 0 && ph > 0); + } + + /* Clipboard: offering text may be refused without input; if accepted, it comes back. */ + int offered = yasl_clipboard_set(YASL_CLIPBOARD, "yasl"); + assert(offered <= 0); + if (offered == 0) { + assert(yasl_clipboard_request(s, YASL_CLIPBOARD) == 0); + bool got = false; + uint64_t clipboard_deadline = yasl_time_ns() + 2000000000ull; + while (!got && yasl_time_ns() < clipboard_deadline) { + assert(yasl_wait_event(&e, 50) >= 0); + if (e.type != YASL_EVENT_CLIPBOARD) + continue; + got = true; + assert(e.surface == s && e.action <= YASL_TRANSFER_FAILED); + if (e.action == YASL_TRANSFER_OK) + assert(e.length == 4 && !strcmp(e.text, "yasl")); + } + assert(got); + } + + /* Destroying the parent also destroys its popup. */ + assert(yasl_surface_destroy(s) == 0); + if (popup) + assert(!listed(display, popup_id)); assert(yasl_init(backend, NULL) == -EALREADY); s = yasl_surface_create(&(struct yasl_surface_options){.title = "shared backend remains", .width = 64, .height = 64}); diff --git a/tests/helpers/run_surfaces.py b/tests/helpers/run_surfaces.py index f120e35..c266b3c 100644 --- a/tests/helpers/run_surfaces.py +++ b/tests/helpers/run_surfaces.py @@ -18,7 +18,8 @@ def wait_for(process, ready): raise TimeoutError('Display server readiness') def main(): - weston, xwayland, contract, hello, tablet, gl, vulkan, notepad = sys.argv[1:] + # Optional trailing binaries run only in the X11 phase. + weston, xwayland, contract, hello, tablet, gl, vulkan, notepad, *x11_only = sys.argv[1:] servers = [] with tempfile.TemporaryDirectory(prefix='yasl-surfaces-') as directory: root = Path(directory) @@ -49,9 +50,12 @@ def main(): if backend == 'x11': case.pop('WAYLAND_DISPLAY', None) case['XDG_SESSION_TYPE'] = 'x11' - for binary, args in ((contract,[backend]), (gl,[backend]), + cases = [(contract,[backend]), (gl,[backend]), (vulkan,[backend]), (tablet,['--backend',backend,'--smoke-test']), - (notepad,['--smoke-test'])): + (notepad,['--smoke-test'])] + if backend == 'x11': + cases += [(binary, []) for binary in x11_only] + for binary, args in cases: if binary == '-': continue print('Testing', Path(binary).name, backend, flush=True) diff --git a/tests/meson.build b/tests/meson.build index 7e1940e..4a0f492 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -6,6 +6,14 @@ foreach kind : ['static', 'shared'] public_test = executable('yasl-' + kind + '-api-test', 'test_lifecycle.c', dependencies: dep) test(kind + '-api', public_test) endforeach +pre_init_test = executable('yasl-public-api-test', 'common/test_public_api.c', dependencies: yasl_dep) +test('public-api', pre_init_test) +foreach std : ['c11', 'c17'] + header_test = executable('yasl-header-' + std + '-test', 'test_header_c11.c', + include_directories: include_directories('../include'), + c_args: ['-pedantic-errors'], override_options: ['c_std=' + std]) + test('header-' + std, header_test) +endforeach if add_languages('cpp', native: false, required: false) cpp_api = executable('yasl-cpp-api-test', 'test_api.cpp', dependencies: shared_dep, cpp_args: ['-pedantic-errors', @@ -76,6 +84,11 @@ if has_x11 test('tablet-x11-native', python, args: [files('helpers/run_x11_tablet.py'), xorg, native_tablet], timeout: 30) endif endif +x11_input_args = [] +if has_x11 and get_option('integration_tests') + x11_input_args += [executable('yasl-input-x11-test', 'x11/test_input.c', + include_directories: inc, dependencies: yasl_dep)] +endif if get_option('integration_tests') display_test_sources = has_cocoa ? files('cocoa/display.m') : [] integration = executable('yasl-surface-integration', ['common/test_surface_integration.c'] + display_test_sources, @@ -93,7 +106,8 @@ if get_option('integration_tests') weston = find_program('weston') xwayland = find_program('Xwayland') test('surface-backends', python, args: [files('helpers/run_surfaces.py'), weston, xwayland, - integration, hello_example, tablet_example, gl_integration, vulkan_integration, notepad_example], timeout: 120) + integration, hello_example, tablet_example, gl_integration, vulkan_integration, notepad_example] + + x11_input_args, timeout: 120) if has_wayland and has_egl reconnect = executable('yasl-reconnect-test', 'wayland/test_reconnect.c', dependencies: yasl_dep, c_args: ['-DTEST_VULKAN=' + (has_vulkan ? '1' : '0')]) diff --git a/tests/test_header_c11.c b/tests/test_header_c11.c new file mode 100644 index 0000000..515a914 --- /dev/null +++ b/tests/test_header_c11.c @@ -0,0 +1,11 @@ +/* Built as strict C11 and C17 with -pedantic-errors: the public header must not need newer C. */ +#include <yasl/yasl.h> +#include <assert.h> + +int main(void) +{ + enum yasl_backend backend = YASL_BACKEND_AUTO; + struct yasl_surface_options options = {.title = "portable", .width = 64, .height = 64}; + assert(backend == 0 && options.type == YASL_SURFACE_CPU && YASL_VERSION_MAJOR == 0); + return 0; +} |
