From dd1775a9927922c14f8679ccbc4d3ca8d5917e20 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 21:23:03 +0300 Subject: public api refactor --- tests/common/test_app.c | 72 ++++++++++--- tests/common/test_events.c | 209 ++++++++++++++++++++++++++++++++++++++ tests/common/test_hello_example.c | 2 +- 3 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 tests/common/test_events.c (limited to 'tests/common') diff --git a/tests/common/test_app.c b/tests/common/test_app.c index f61a957..214d8f2 100644 --- a/tests/common/test_app.c +++ b/tests/common/test_app.c @@ -2,6 +2,7 @@ #include "yasl/app.h" #include +#include #include #include #include @@ -16,13 +17,16 @@ struct test_state { bool quit_called; int frame_count; yasl_timer_id timer_id; + pthread_t worker; + uint64_t target_id; }; static void *worker_thread(void *arg) { struct test_state *state = arg; usleep(50000); /* 50ms */ - bool ok = yasl_post_event(state->display, state->surface, 42, (void *)0x1234, 0); + bool ok = yasl_display_post(state->display, &(struct yasl_message){ + .target_id = state->target_id, .code = 42, .payload = (void *)0x1234}); assert(ok); return NULL; } @@ -54,13 +58,13 @@ static enum yasl_app_result on_init(void *ud, struct yasl_display *d) yasl_surface_set_pointer_mode(state->surface, YASL_POINTER_RELATIVE); yasl_surface_set_pointer_mode(state->surface, YASL_POINTER_NORMAL); - state->timer_id = yasl_app_add_timer(d, YASL_MS_TO_NS(20), on_timer, state); + state->timer_id = yasl_loop_add_timer(yasl_display_loop(d), YASL_MS_TO_NS(20), on_timer, state); assert(state->timer_id > 0); - pthread_t thread; - int err = pthread_create(&thread, NULL, worker_thread, state); + state->target_id = yasl_surface_id(state->surface); + int err = pthread_create(&state->worker, NULL, worker_thread, state); assert(err == 0); - pthread_detach(thread); + yasl_surface_request_frame(state->surface); return YASL_APP_CONTINUE; @@ -80,7 +84,7 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) return YASL_APP_CONTINUE; } -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame_context *ctx) +static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) { struct test_state *state = ud; assert(s == state->surface); @@ -88,12 +92,12 @@ static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame_context state->frame_called = true; state->frame_count++; - if (ctx->cpu.pixels) { - size_t count = (size_t)ctx->size.x * ctx->size.y; + if (ctx->target.pixels) { + size_t count = (size_t)ctx->target.size.x * ctx->target.size.y; for (size_t i = 0; i < count; i++) { - ctx->cpu.pixels[i] = 0xff204060; + ctx->target.pixels[i] = 0xff204060; } - bool ok = yasl_frame_present_cpu(ctx, NULL, 0); + bool ok = yasl_frame_present(ctx, NULL, 0); assert(ok); } @@ -106,14 +110,15 @@ static void on_quit(void *ud, struct yasl_display *d) { (void)d; struct test_state *state = ud; + assert(!pthread_join(state->worker, NULL)); state->quit_called = true; } -static void present_once(void *ud, struct yasl_surface *s, struct yasl_frame_context *ctx) +static void present_once(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) { (*(int *)ud)++; - if (ctx->cpu.pixels) - assert(yasl_frame_present_cpu(ctx, NULL, 0)); + if (ctx->target.pixels) + assert(yasl_frame_present(ctx, NULL, 0)); yasl_surface_destroy(s); } @@ -132,6 +137,43 @@ static void test_window_without_init(void) assert(frames == 1); } + +static enum yasl_app_result fail_init(void *ud, struct yasl_display *d) +{ + (void)ud; (void)d; + return YASL_APP_ERROR; +} +static void count_quit(void *ud, struct yasl_display *d) +{ + (void)d; + (*(int *)ud)++; +} +static enum yasl_app_result inspect_initial_window(void *ud, struct yasl_display *d) +{ + struct yasl_surface *s = yasl_app_window(d); + assert(s && yasl_surface_userdata(s) == ud); + errno = 0; + assert(!yasl_surface_set_callbacks(s, NULL, NULL) && errno == EBUSY); + return YASL_APP_CONTINUE; +} +static void fail_frame(void *ud, struct yasl_surface *s, struct yasl_frame *frame) +{ + (void)ud; + assert(frame->surface == s && frame->target.stride_bytes == frame->target.size.x * 4); + yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); +} +static void test_error_lifecycle(void) +{ + int quits = 0; + assert(yasl_run(&(struct yasl_app_desc){.userdata = &quits, + .init = fail_init, .quit = count_quit}) < 0); + assert(quits == 1 && errno == EIO); + struct yasl_window_spec window = {.size_logical = {160, 120}, .userdata = &quits}; + assert(yasl_run(&(struct yasl_app_desc){.userdata = &quits, .window = &window, + .init = inspect_initial_window, .frame = fail_frame, .quit = count_quit}) < 0); + assert(quits == 2 && errno == EIO); +} + int main(void) { struct test_state state = {0}; @@ -146,6 +188,9 @@ int main(void) }; int res = yasl_run(&desc); + if (res < 0 && !state.init_called && + (errno == EPERM || errno == EACCES || errno == ENOENT || + errno == ECONNREFUSED || errno == ENODEV)) return 77; assert(res == 0); assert(state.init_called); assert(state.frame_called); @@ -153,6 +198,7 @@ int main(void) assert(state.timer_called); assert(state.quit_called); test_window_without_init(); + test_error_lifecycle(); printf("App runner tests passed!\n"); return 0; } diff --git a/tests/common/test_events.c b/tests/common/test_events.c new file mode 100644 index 0000000..c8660cb --- /dev/null +++ b/tests/common/test_events.c @@ -0,0 +1,209 @@ +#include "common/backend.h" +#include "yasl/app.h" + +#include +#include +#include +#include +#include + +static struct input_queue *queue(struct yasl_surface *s) { return &s->input; } +static const struct yasl_backend_ops ops = {.surface_input_queue = queue}; +static int released; +static pthread_t owner; +static void release(void *payload) +{ + assert(pthread_equal(owner, pthread_self())); + assert(payload == &released); + released++; +} + +static void setup(struct yasl_display *d, struct yasl_surface *a, struct yasl_surface *b) +{ + *d = (struct yasl_display){.ops = &ops}; + *a = (struct yasl_surface){.display = d, .scale_120 = 120}; + *b = (struct yasl_surface){.display = d, .scale_120 = 120}; + assert(yasl_events_open(d)); + assert(yasl_events_register(a)); + assert(yasl_events_register(b)); +} + +static void teardown(struct yasl_display *d, struct yasl_surface *a, struct yasl_surface *b) +{ + yasl_events_unregister(a); + yasl_events_unregister(b); + input_queue_clear(&a->input); + input_queue_clear(&b->input); + yasl_events_close(d); +} + +static void test_order_and_lifetimes(void) +{ + struct yasl_display d; + struct yasl_surface a, b; + setup(&d, &a, &b); + uint64_t aid = yasl_surface_id(&a), bid = yasl_surface_id(&b); + assert(aid && bid && aid != bid); + yasl_surface_set_userdata(&a, &b); + assert(yasl_surface_userdata(&a) == &b); + char text[] = "text"; + assert(input_queue_push(&a.input, &(struct yasl_event){ + .type = YASL_EVENT_TEXT, .surface = &a, .text = {text, 4}})); + memset(text, 'x', 4); + yasl_surface_notify_state(&b, YASL_STATE_MAXIMIZED); + yasl_surface_notify_configure(&a, (struct yasl_geometry){{640, 480}, 180}); + yasl_surface_notify_drag(&b, YASL_DRAG_OVER, (struct yasl_vector2i){3, 4}); + yasl_surface_notify_composition(&a, "hello", 1, 3); + yasl_surface_notify_composition(&a, NULL, -1, -1); + yasl_surface_complete_paste(&b, 42, YASL_TRANSFER_UNAVAILABLE, NULL, 0); + const enum yasl_event_type expected[] = {YASL_EVENT_TEXT, YASL_EVENT_WINDOW_STATE, + YASL_EVENT_WINDOW_RESIZE, YASL_EVENT_DRAG, YASL_EVENT_COMPOSITION, + YASL_EVENT_COMPOSITION, YASL_EVENT_PASTE}; + struct yasl_event ev; + uint64_t sequence = 0; + for (size_t i = 0; i < sizeof(expected) / sizeof(*expected); i++) { + assert(yasl_display_poll_event(&d, &ev)); + assert(ev.type == expected[i] && ev.sequence > sequence && ev.time_ns); + sequence = ev.sequence; + if (i == 0) assert(!strcmp(ev.text.data, "text") && ev.surface_id == aid); + if (i == 2) assert(ev.resize.scale_changed && ev.resize.geometry.size.x == 640); + if (i == 4) assert(ev.composition.active && ev.composition.begin == 1 && !strcmp(ev.composition.data, "hello")); + if (i == 5) assert(!ev.composition.active && !ev.composition.len); + if (i == 6) assert(ev.paste.request_id == 42 && ev.paste.status == YASL_TRANSFER_UNAVAILABLE); + } + assert(!yasl_display_poll_event(&d, &ev)); + assert(input_queue_push(&a.input, &(struct yasl_event){ + .type = YASL_EVENT_TEXT, .surface = &a, .text = {"kept", 4}})); + assert(yasl_display_poll_event(&d, &ev)); + const char *borrowed = ev.text.data; + assert(input_queue_push(&a.input, &(struct yasl_event){.type = YASL_EVENT_KEY, .surface = &a})); + yasl_events_unregister(&a); + assert(!strcmp(borrowed, "kept")); + assert(!yasl_display_poll_event(&d, &ev)); + /* Reusing object memory must never reuse the identity. */ + assert(yasl_events_register(&a)); + assert(a.id != aid); + errno = 0; + assert(!yasl_display_post(&d, &(struct yasl_message){.target_id = aid})); + assert(errno == ENOENT); + teardown(&d, &a, &b); +} + +struct producer { struct yasl_display *display; uint64_t target; }; +static void *post_worker(void *ud) +{ + struct producer *producer = ud; + for (uint32_t i = 0; i < 32; i++) + assert(yasl_display_post(producer->display, &(struct yasl_message){ + .target_id = producer->target, .code = i, .payload = &released, .release = release})); + return NULL; +} + +static void test_post_cleanup(void) +{ + struct yasl_display d; + struct yasl_surface a, b; + setup(&d, &a, &b); + owner = pthread_self(); + released = 0; + pthread_t worker; + struct producer producer = {&d, a.id}; + assert(!pthread_create(&worker, NULL, post_worker, &producer)); + assert(!pthread_join(worker, NULL)); + struct yasl_event ev; + for (uint32_t i = 0; i < 8; i++) { + assert(yasl_display_poll_event(&d, &ev)); + assert(ev.user.code == i && ev.surface == &a); + } + assert(released == 7); /* Last event remains borrowed. */ + yasl_events_unregister(&a); + assert(released == 31); /* Undelivered target messages canceled. */ + assert(!yasl_display_poll_event(&d, &ev)); + assert(yasl_display_post(&d, &(struct yasl_message){.payload = &released, .release = release})); + yasl_events_close(&d); + assert(released == 33); /* Pending message and last consumed message. */ + errno = 0; + assert(!yasl_display_post(&d, &(struct yasl_message){.payload = &released, .release = release})); + assert(errno == EINVAL && released == 33); +} + +static int damage_count; +static bool present(struct yasl_surface *s, const struct yasl_framebuffer *target, + const struct yasl_damage_list *damage) +{ + (void)s; (void)target; + damage_count = damage->n; + return true; +} +static void test_damage(void) +{ + struct yasl_backend_ops present_ops = {.surface_present = present}; + struct yasl_display d = {.ops = &present_ops}; + struct yasl_surface s = {.display = &d, .frame_generation = 1, .drawing = true}; + struct yasl_frame frame = {.surface = &s, .target = {.generation = 1, .size = {8, 8}}}; + s.active_frame = &frame; + struct yasl_rect2i rects[YASL_DAMAGE_MAX + 1] = {{0}}; + assert(yasl_frame_present(&frame, NULL, 0) && damage_count == -1); + assert(!yasl_frame_present(&frame, NULL, 0) && errno == EALREADY); + s.frame_presented = false; + assert(yasl_frame_present(&frame, rects, 0) && damage_count == 0); + s.frame_presented = false; + assert(yasl_frame_present(&frame, rects, YASL_DAMAGE_MAX + 1) && damage_count == -1); + s.frame_presented = false; + rects[YASL_DAMAGE_MAX].width = -1; + errno = 0; + assert(!yasl_frame_present(&frame, rects, YASL_DAMAGE_MAX + 1) && errno == EINVAL); + assert(!yasl_frame_present(&frame, NULL, 1)); + s.drawing = false; + assert(!yasl_frame_present(&frame, NULL, 0)); +} + +static void test_failures(void) +{ + struct yasl_display d; + struct yasl_surface a, b; + setup(&d, &a, &b); + assert(!yasl_surface_set_size(&a, (struct yasl_vector2i){0, 10}) && errno == EINVAL); + assert(!yasl_surface_set_size(&a, (struct yasl_vector2i){10, 10}) && errno == ENOTSUP); + assert(!yasl_surface_set_fullscreen(&a, true) && errno == ENOTSUP); + assert(!yasl_surface_set_maximized(&a, true) && errno == ENOTSUP); + assert(!yasl_surface_minimize(&a) && errno == ENOTSUP); + a.popup = true; + assert(!yasl_surface_minimize(&a) && errno == EINVAL); + assert(!yasl_clipboard_set(&d, YASL_CLIPBOARD, "text") && errno == ENOTSUP); + assert(!yasl_clipboard_request(&a, YASL_CLIPBOARD) && errno == ENOTSUP); + /* Queued native event allocation/validation failure must be observable. */ + assert(!yasl_events_push(&d, &a, &(struct yasl_event){ + .type = YASL_EVENT_TEXT, .surface = &a, .text = {"x", SIZE_MAX}})); + assert(yasl_events_error(&d) == ENOMEM); + teardown(&d, &a, &b); +} + + +static void test_legacy_poll_does_not_spin(void) +{ + struct yasl_display d; + struct yasl_surface a, b; + setup(&d, &a, &b); + yasl_surface_notify_state(&a, YASL_STATE_ACTIVATED); + yasl_surface_notify_close(&b); + assert(yasl_events_pending(&d)); + struct yasl_event ev; + assert(!yasl_surface_poll_event(&a, &ev)); + assert(!yasl_events_pending(&d)); + assert(input_queue_push(&b.input, &(struct yasl_event){.type = YASL_EVENT_KEY, .surface = &b})); + assert(!yasl_surface_poll_event(&a, &ev)); + assert(yasl_surface_poll_event(&b, &ev) && ev.surface == &b); + assert(!yasl_events_pending(&d)); + teardown(&d, &a, &b); +} + +int main(void) +{ + test_order_and_lifetimes(); + test_post_cleanup(); + test_damage(); + test_failures(); + test_legacy_poll_does_not_spin(); + return 0; +} diff --git a/tests/common/test_hello_example.c b/tests/common/test_hello_example.c index 101e2d4..350b3bb 100644 --- a/tests/common/test_hello_example.c +++ b/tests/common/test_hello_example.c @@ -3,7 +3,7 @@ #include "../../examples/hello.c" #undef main -static void draw_once(void *ud, struct yasl_surface *surface, struct yasl_frame_context *frame) +static void draw_once(void *ud, struct yasl_surface *surface, struct yasl_frame *frame) { draw(ud, surface, frame); if (!failed) yasl_surface_destroy(surface); -- cgit v1.2.3