/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2014-2017 Red Hat, Inc. * * This program 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 2 of the * License, or (at your option) any later version. * * This program 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 this program; if not, see . */ #include "config.h" #include "tests/test-utils.h" #include #include #include "core/display-private.h" #include "core/window-private.h" #include "wayland/meta-wayland.h" #include "wayland/meta-xwayland.h" #include "x11/meta-x11-display-private.h" struct _TestClient { char *id; MetaWindowClientType type; GSubprocess *subprocess; GCancellable *cancellable; GMainLoop *loop; GDataOutputStream *in; GDataInputStream *out; char *line; GError **error; AsyncWaiter *waiter; }; struct _AsyncWaiter { XSyncCounter counter; int counter_value; XSyncAlarm alarm; GMainLoop *loop; int counter_wait_value; }; G_DEFINE_QUARK (test-runner-error-quark, test_runner_error) static char *test_client_path; static void ensure_test_client_path (int argc, char **argv) { test_client_path = g_test_build_filename (G_TEST_BUILT, "src", "tests", "mutter-test-client", NULL); if (!g_file_test (test_client_path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE)) { g_autofree char *basename = NULL; g_autofree char *dirname = NULL; basename = g_path_get_basename (argv[0]); dirname = g_path_get_dirname (argv[0]); test_client_path = g_build_filename (dirname, "mutter-test-client", NULL); } if (!g_file_test (test_client_path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE)) g_error ("mutter-test-client executable not found"); } void test_init (int *argc, char ***argv) { g_test_init (argc, argv, NULL); g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id="); ensure_test_client_path (*argc, *argv); meta_wayland_override_display_name ("mutter-test-display"); meta_xwayland_override_display_number (512); } AsyncWaiter * async_waiter_new (void) { AsyncWaiter *waiter = g_new0 (AsyncWaiter, 1); MetaDisplay *display = meta_get_display (); Display *xdisplay = display->x11_display->xdisplay; XSyncValue value; XSyncAlarmAttributes attr; waiter->counter_value = 0; XSyncIntToValue (&value, waiter->counter_value); waiter->counter = XSyncCreateCounter (xdisplay, value); attr.trigger.counter = waiter->counter; attr.trigger.test_type = XSyncPositiveComparison; /* Initialize to one greater than the current value */ attr.trigger.value_type = XSyncRelative; XSyncIntToValue (&attr.trigger.wait_value, 1); /* After triggering, increment test_value by this until * until the test condition is false */ XSyncIntToValue (&attr.delta, 1); /* we want events (on by default anyway) */ attr.events = True; waiter->alarm = XSyncCreateAlarm (xdisplay, XSyncCACounter | XSyncCAValueType | XSyncCAValue | XSyncCATestType | XSyncCADelta | XSyncCAEvents, &attr); waiter->loop = g_main_loop_new (NULL, FALSE); return waiter; } void async_waiter_destroy (AsyncWaiter *waiter) { MetaDisplay *display = meta_get_display (); Display *xdisplay = display->x11_display->xdisplay; XSyncDestroyAlarm (xdisplay, waiter->alarm); XSyncDestroyCounter (xdisplay, waiter->counter); g_main_loop_unref (waiter->loop); } static int async_waiter_next_value (AsyncWaiter *waiter) { return waiter->counter_value + 1; } static void async_waiter_wait (AsyncWaiter *waiter, int wait_value) { if (waiter->counter_value < wait_value) { waiter->counter_wait_value = wait_value; g_main_loop_run (waiter->loop); waiter->counter_wait_value = 0; } } void async_waiter_set_and_wait (AsyncWaiter *waiter) { MetaDisplay *display = meta_get_display (); Display *xdisplay = display->x11_display->xdisplay; int wait_value = async_waiter_next_value (waiter); XSyncValue sync_value; XSyncIntToValue (&sync_value, wait_value); XSyncSetCounter (xdisplay, waiter->counter, sync_value); async_waiter_wait (waiter, wait_value); } gboolean async_waiter_alarm_filter (MetaX11Display *x11_display, XSyncAlarmNotifyEvent *event, gpointer data) { AsyncWaiter *waiter = data; if (event->alarm != waiter->alarm) return FALSE; waiter->counter_value = XSyncValueLow32 (event->counter_value); if (waiter->counter_wait_value != 0 && waiter->counter_value >= waiter->counter_wait_value) g_main_loop_quit (waiter->loop); return TRUE; } char * test_client_get_id (TestClient *client) { return client->id; } static void test_client_line_read (GObject *source, GAsyncResult *result, gpointer data) { TestClient *client = data; client->line = g_data_input_stream_read_line_finish_utf8 (client->out, result, NULL, client->error); g_main_loop_quit (client->loop); } gboolean test_client_do (TestClient *client, GError **error, ...) { GString *command = g_string_new (NULL); char *line = NULL; va_list vap; va_start (vap, error); while (TRUE) { char *word = va_arg (vap, char *); char *quoted; if (word == NULL) break; if (command->len > 0) g_string_append_c (command, ' '); quoted = g_shell_quote (word); g_string_append (command, quoted); g_free (quoted); } va_end (vap); g_string_append_c (command, '\n'); if (!g_data_output_stream_put_string (client->in, command->str, client->cancellable, error)) goto out; g_data_input_stream_read_line_async (client->out, G_PRIORITY_DEFAULT, client->cancellable, test_client_line_read, client); client->error = error; g_main_loop_run (client->loop); line = client->line; client->line = NULL; client->error = NULL; if (!line) { if (*error == NULL) g_set_error (error, TEST_RUNNER_ERROR, TEST_RUNNER_ERROR_RUNTIME_ERROR, "test client exited"); goto out; } if (strcmp (line, "OK") != 0) { g_set_error (error, TEST_RUNNER_ERROR, TEST_RUNNER_ERROR_RUNTIME_ERROR, "%s", line); goto out; } out: g_string_free (command, TRUE); g_free (line); return *error == NULL; } gboolean test_client_wait (TestClient *client, GError **error) { if (client->type == META_WINDOW_CLIENT_TYPE_WAYLAND) { return test_client_do (client, error, "sync", NULL); } else { int wait_value = async_waiter_next_value (client->waiter); char *counter_str = g_strdup_printf ("%lu", client->waiter->counter); char *wait_value_str = g_strdup_printf ("%d", wait_value); gboolean success; success = test_client_do (client, error, "set_counter", counter_str, wait_value_str, NULL); g_free (counter_str); g_free (wait_value_str); if (!success) return FALSE; async_waiter_wait (client->waiter, wait_value); return TRUE; } } MetaWindow * test_client_find_window (TestClient *client, const char *window_id, GError **error) { MetaDisplay *display = meta_get_display (); GSList *windows; GSList *l; MetaWindow *result; char *expected_title; windows = meta_display_list_windows (display, META_LIST_INCLUDE_OVERRIDE_REDIRECT); expected_title = g_strdup_printf ("test/%s/%s", client->id, window_id); result = NULL; for (l = windows; l; l = l->next) { MetaWindow *window = l->data; if (g_strcmp0 (window->title, expected_title) == 0) { result = window; break; } } g_slist_free (windows); g_free (expected_title); if (result == NULL) g_set_error (error, TEST_RUNNER_ERROR, TEST_RUNNER_ERROR_RUNTIME_ERROR, "window %s/%s isn't known to Mutter", client->id, window_id); return result; } typedef struct _WaitForShownData { GMainLoop *loop; MetaWindow *window; gulong shown_handler_id; } WaitForShownData; static void on_window_shown (MetaWindow *window, WaitForShownData *data) { g_main_loop_quit (data->loop); } static gboolean wait_for_showing_before_redraw (gpointer user_data) { WaitForShownData *data = user_data; if (meta_window_is_hidden (data->window)) { data->shown_handler_id = g_signal_connect (data->window, "shown", G_CALLBACK (on_window_shown), data); } else { g_main_loop_quit (data->loop); } return FALSE; } void test_client_wait_for_window_shown (TestClient *client, MetaWindow *window) { WaitForShownData data = { .loop = g_main_loop_new (NULL, FALSE), .window = window, }; meta_later_add (META_LATER_BEFORE_REDRAW, wait_for_showing_before_redraw, &data, NULL); g_main_loop_run (data.loop); g_clear_signal_handler (&data.shown_handler_id, window); g_main_loop_unref (data.loop); } gboolean test_client_alarm_filter (MetaX11Display *x11_display, XSyncAlarmNotifyEvent *event, gpointer data) { TestClient *client = data; if (client->waiter) return async_waiter_alarm_filter (x11_display, event, client->waiter); else return FALSE; } static gpointer spawn_xwayland (gpointer user_data) { xcb_connection_t *connection; connection = xcb_connect (NULL, NULL); g_assert_nonnull (connection); xcb_disconnect (connection); return NULL; } TestClient * test_client_new (const char *id, MetaWindowClientType type, GError **error) { TestClient *client; GSubprocessLauncher *launcher; GSubprocess *subprocess; MetaWaylandCompositor *compositor; const char *wayland_display_name; const char *x11_display_name; launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE)); g_assert (meta_is_wayland_compositor ()); compositor = meta_wayland_compositor_get_default (); wayland_display_name = meta_wayland_get_wayland_display_name (compositor); x11_display_name = meta_wayland_get_public_xwayland_display_name (compositor); g_subprocess_launcher_setenv (launcher, "WAYLAND_DISPLAY", wayland_display_name, TRUE); g_subprocess_launcher_setenv (launcher, "DISPLAY", x11_display_name, TRUE); subprocess = g_subprocess_launcher_spawn (launcher, error, test_client_path, "--client-id", id, (type == META_WINDOW_CLIENT_TYPE_WAYLAND ? "--wayland" : NULL), NULL); g_object_unref (launcher); if (!subprocess) return NULL; client = g_new0 (TestClient, 1); client->type = type; client->id = g_strdup (id); client->cancellable = g_cancellable_new (); client->subprocess = subprocess; client->in = g_data_output_stream_new (g_subprocess_get_stdin_pipe (subprocess)); client->out = g_data_input_stream_new (g_subprocess_get_stdout_pipe (subprocess)); client->loop = g_main_loop_new (NULL, FALSE); if (client->type == META_WINDOW_CLIENT_TYPE_X11) { MetaDisplay *display = meta_get_display (); if (!display->x11_display) { GThread *thread; thread = g_thread_new ("Mutter Spawn Xwayland Thread", spawn_xwayland, NULL); test_wait_for_x11_display (); g_thread_join (thread); } client->waiter = async_waiter_new (); } return client; } gboolean test_client_quit (TestClient *client, GError **error) { if (!test_client_do (client, error, "destroy_all", NULL)) return FALSE; if (!test_client_wait (client, error)) return FALSE; return TRUE; } void test_client_destroy (TestClient *client) { GError *error = NULL; if (client->waiter) async_waiter_destroy (client->waiter); g_output_stream_close (G_OUTPUT_STREAM (client->in), NULL, &error); if (error) { g_warning ("Error closing client stdin: %s", error->message); g_clear_error (&error); } g_object_unref (client->in); g_input_stream_close (G_INPUT_STREAM (client->out), NULL, &error); if (error) { g_warning ("Error closing client stdout: %s", error->message); g_clear_error (&error); } g_object_unref (client->out); g_object_unref (client->cancellable); g_object_unref (client->subprocess); g_main_loop_unref (client->loop); g_free (client->id); g_free (client); } const char * test_get_plugin_name (void) { const char *name; name = g_getenv ("MUTTER_TEST_PLUGIN_PATH"); if (name) return name; else return "libdefault"; } void test_wait_for_x11_display (void) { MetaDisplay *display; display = meta_get_display (); g_assert_nonnull (display); while (!display->x11_display) g_main_context_iteration (NULL, TRUE); g_assert_nonnull (display->x11_display); }