mirror of
https://github.com/brl/mutter.git
synced 2024-11-23 16:40:41 -05:00
506e06589b
test_client_new might return early if conditions are not met, leaving some allocated data around without freeing it. Since we're not using the client before, there's no need to initialize it early and just initialize it when it's going to be returned. https://gitlab.gnome.org/GNOME/mutter/merge_requests/1195
551 lines
14 KiB
C
551 lines
14 KiB
C
/* -*- 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "tests/test-utils.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
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_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)
|
|
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);
|
|
}
|