diff --git a/tests/select_helper.c b/tests/select_helper.c new file mode 100644 index 0000000..a2cdf70 --- /dev/null +++ b/tests/select_helper.c @@ -0,0 +1,201 @@ +/* Tests for the Wayland compositor running on the X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include +#include +#include + +#include +#include + +/* select_helper.c -- Read the data of the clipboard selection and + print them to stdout. + + There must be three arguments: the name of the display, the + timestamp at which the selection was acquired, and the target. */ + + +/* The display connected to. */ +static Display *display; + +/* The selection transfer window. */ +static Window selection_transfer_window; + +/* Various atoms. */ +static Atom CLIPBOARD, target_atom, INCR; + +static void +wait_for_selection_notify (XEvent *event) +{ + while (true) + { + XNextEvent (display, event); + + if (event->type == SelectionNotify + && (event->xselection.requestor + == selection_transfer_window) + && (event->xselection.selection + == CLIPBOARD) + && (event->xselection.property + == target_atom) + && (event->xselection.target + == target_atom)) + return; + } +} + +static void +wait_for_new_value (XEvent *event, Atom property) +{ + while (true) + { + XNextEvent (display, event); + + if (event->type == PropertyNotify + && event->xproperty.atom == property + && event->xproperty.state == PropertyNewValue) + return; + } +} + +static size_t +get_size_for_format (int format) +{ + switch (format) + { + case 32: + return sizeof (long); + + case 16: + return sizeof (short int); + + case 8: + return sizeof (char); + } + + /* Should not actually happen. */ + return 0; +} + +int +main (int argc, char **argv) +{ + XSetWindowAttributes attrs; + unsigned long flags, timestamp; + char *atom_names[2]; + Atom atoms[3], actual_type, property; + XEvent event; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *data; + + if (argc < 4) + /* Not enough arguments were specified. */ + return 1; + + display = XOpenDisplay (argv[1]); + + if (!display) + return 1; + + /* Make the window used to transfer selection data. */ + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask; + flags = CWEventMask | CWOverrideRedirect; + + selection_transfer_window + = XCreateWindow (display, DefaultRootWindow (display), + -1, -1, 1, 1, 0, CopyFromParent, InputOnly, + CopyFromParent, flags, &attrs); + + /* Get the time. */ + timestamp = strtoul (argv[2], NULL, 10); + + atom_names[0] = argv[3]; + atom_names[1] = (char *) "CLIPBOARD"; + atom_names[2] = (char *) "CLIPBOARD"; + XInternAtoms (display, atom_names, 3, False, atoms); + target_atom = atoms[0]; + CLIPBOARD = atoms[1]; + INCR = atoms[2]; + + /* Now ask for CLIPBOARD. */ + XConvertSelection (display, CLIPBOARD, target_atom, + target_atom, selection_transfer_window, + timestamp); + + /* And wait for the SelectionNotify event. */ + wait_for_selection_notify (&event); + + /* Selection conversion failed. */ + if (event.xselection.property == None) + return 1; + + property = event.xselection.property; + + XGetWindowProperty (display, selection_transfer_window, + property, 0, 0xffffffff, True, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, + &data); + + if (!data || bytes_after) + return 1; + + if (actual_type == INCR) + { + while (true) + { + XFree (data); + + wait_for_new_value (&event, property); + XGetWindowProperty (display, selection_transfer_window, property, 0, + 0xffffffff, True, AnyPropertyType, &actual_type, + &actual_format, &nitems, &bytes_after, &data); + + if (!data) + return 0; + + if (nitems) + { + /* Write the selection data to stdout. */ + if (fwrite (data, get_size_for_format (actual_format), + nitems, stdout) != nitems) + return 1; + + continue; + } + + /* Selection transfer is complete. */ + fflush (stdout); + return 0; + } + } + else + { + /* Write the selection data to stdout. */ + if (fwrite (data, get_size_for_format (actual_format), + nitems, stdout) != nitems) + return 1; + + /* Return success. */ + fflush (stdout); + return 0; + } +} diff --git a/tests/select_test.c b/tests/select_test.c new file mode 100644 index 0000000..626c9b8 --- /dev/null +++ b/tests/select_test.c @@ -0,0 +1,311 @@ +/* Tests for the Wayland compositor running on the X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include + +#include "test_harness.h" + +#include + +enum test_kind + { + SELECT_STRING_KIND, + }; + +static const char *test_names[] = + { + "select_string", + }; + +#define LAST_TEST SELECT_STRING_KIND + +/* The display. */ +static struct test_display *display; + +/* The data device manager. */ +static struct wl_data_device_manager *data_device_manager; + +/* Test interfaces. */ +static struct test_interface test_interfaces[] = + { + { "wl_data_device_manager", &data_device_manager, + &wl_data_device_manager_interface, 3, }, + }; + +/* The data device. */ +static struct wl_data_device *data_device; + +/* Whether or not the data source's send listener was called. */ +static bool send_called; + +/* Sample text used. */ + +#define SAMPLE_TEXT \ + ("Lorem ipsum dolor sit amet, consectetur adipiscing elit" \ + ", sed do eiusmod tempor incididunt ut labore et dolore" \ + " magna aliqua. Ut enim ad minim veniam, quis nostrud" \ + " exercitation ullamco laboris nisi ut aliquip ex ea commodo" \ + " consequat. Duis aute irure dolor in reprehenderit in" \ + " voluptate velit esse cillum dolore eu fugiat nulla pariatur." \ + " Excepteur sint occaecat cupidatat non proident, sunt in" \ + " culpa qui officia deserunt mollit anim id est laborum.") + + + +static Bool +test_get_time_1 (Display *display, XEvent *event, XPointer arg) +{ + Atom *atom; + + atom = (Atom *) arg; + + if (event->type == PropertyNotify + && event->xproperty.atom == *atom) + return True; + + return False; +} + +/* Get a timestamp suitable for use in events dispatched to the test + seat. */ + +static Time +test_get_time (void) +{ + Atom property_atom; + XEvent event; + Window window; + unsigned char unused; + XSetWindowAttributes attrs; + + attrs.event_mask = PropertyChangeMask; + window = XCreateWindow (display->x_display, + DefaultRootWindow (display->x_display), + 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, + CWEventMask, &attrs); + unused = '\0'; + property_atom = XInternAtom (display->x_display, + "_INTERNAL_SERVER_TIME_PROP", + False); + XChangeProperty (display->x_display, window, property_atom, + XA_CARDINAL, 8, PropModeReplace, &unused, 1); + XIfEvent (display->x_display, &event, test_get_time_1, + (XPointer) &property_atom); + XDestroyWindow (display->x_display, window); + return event.xproperty.time; +} + + + +static void +handle_data_source_target (void *data, struct wl_data_source *data_source, + const char *mime_type) +{ + /* Nothing to do here. */ +} + +static void * +handle_data_source_send_1 (void *data) +{ + int fd; + + fd = (intptr_t) data; + + write (fd, SAMPLE_TEXT, sizeof SAMPLE_TEXT - 1); + close (fd); + + return NULL; +} + +static void +handle_data_source_send (void *data, struct wl_data_source *data_source, + const char *mime_type, int fd) +{ + pthread_t thread; + + send_called = true; + + if (pthread_create (&thread, NULL, handle_data_source_send_1, + (void *) (intptr_t) fd)) + die ("pthread_create"); +} + +static void +handle_data_source_cancelled (void *data, struct wl_data_source *data_source) +{ + report_test_failure ("data source cancelled"); +} + +static const struct wl_data_source_listener data_source_listener = + { + handle_data_source_target, + handle_data_source_send, + handle_data_source_cancelled, + }; + + + +static void +own_sample_text (void) +{ + struct wl_data_source *source; + uint32_t display_serial; + + source = wl_data_device_manager_create_data_source (data_device_manager); + display_serial = test_get_serial (display); + + if (!source) + report_test_failure ("failed to create data source"); + + wl_data_source_offer (source, "text/plain"); + wl_data_source_offer (source, "text/plain;charset=utf-8"); + wl_data_source_add_listener (source, &data_source_listener, NULL); + wl_data_device_set_selection (data_device, source, display_serial); +} + +static void +verify_sample_text (Time time) +{ + int pipefds[2], wstatus; + pid_t pid; + char *display_string; + char time_buffer[45], buffer[sizeof SAMPLE_TEXT]; + ssize_t bytes_read; + const char *sample_text_buffer; + + /* Run select_helper with the specified timestamp. Wait until + handle_data_source_send is called, and then begin reading from + the pipe. */ + + if (pipe (pipefds) < 0) + die ("pipe"); + + display_string = DisplayString (display->x_display); + time = sprintf (time_buffer, "%lu", time); + pid = fork (); + + if (pid == -1) + die ("fork"); + else if (!pid) + { + close (pipefds[0]); + + if (!dup2 (pipefds[1], 1)) + exit (1); + + execlp ("./select_helper", "./select_helper", + display_string, time_buffer, "STRING", + NULL); + exit (1); + } + + send_called = false; + + while (!send_called) + { + if (wl_display_dispatch (display->display) == -1) + die ("wl_display_dispatch"); + } + + /* Now, start reading from the pipe and comparing the contents. */ + sample_text_buffer = SAMPLE_TEXT; + bytes_read = read (pipefds[0], buffer, sizeof SAMPLE_TEXT - 1); + + if (bytes_read != sizeof SAMPLE_TEXT - 1) + report_test_failure ("wanted %zu bytes, but got %zd", + sizeof SAMPLE_TEXT - 1, bytes_read); + + waitpid (pid, &wstatus, 0); + + if (WEXITSTATUS (wstatus)) + report_test_failure ("child exited with failure: %d", + WEXITSTATUS (wstatus)); + + /* Now compare the text. */ + if (memcmp (buffer, sample_text_buffer, bytes_read)) + report_test_failure ("read text differs from sample text!"); + + close (pipefds[0]); + close (pipefds[1]); +} + + + +static void +test_single_step (enum test_kind kind) +{ + Time time; + + test_log ("running test step: %s", test_names[kind]); + + switch (kind) + { + case SELECT_STRING_KIND: + /* Set the last user time of the seat to the current X server + time. */ + time = test_get_time (); + test_seat_controller_set_last_user_time (display->seat->controller, + 0, time); + own_sample_text (); + + /* Do a roundtrip. If selection ownership changes, then the + protocol translator will wait for selection ownership to be + confirmed. */ + wl_display_roundtrip (display->display); + + /* Now, verify the selection contents. */ + verify_sample_text (time); + break; + } + + if (kind == LAST_TEST) + test_complete (); +} + + + +static void +run_test (void) +{ + test_single_step (SELECT_STRING_KIND); + + while (true) + { + if (wl_display_dispatch (display->display) == -1) + die ("wl_display_dispatch"); + } +} + +int +main (void) +{ + test_init (); + display = open_test_display (test_interfaces, + ARRAYELTS (test_interfaces)); + + if (!display) + report_test_failure ("failed to open display"); + + test_init_seat (display); + data_device + = wl_data_device_manager_get_data_device (data_device_manager, + display->seat->seat); + run_test (); +}