diff --git a/src/backends/native/meta-thread-impl.h b/src/backends/native/meta-thread-impl.h index 776755889..5f13aa38b 100644 --- a/src/backends/native/meta-thread-impl.h +++ b/src/backends/native/meta-thread-impl.h @@ -22,9 +22,12 @@ #include +#include "core/util-private.h" + typedef struct _MetaThread MetaThread; #define META_TYPE_THREAD_IMPL (meta_thread_impl_get_type ()) +META_EXPORT_TEST G_DECLARE_DERIVABLE_TYPE (MetaThreadImpl, meta_thread_impl, META, THREAD_IMPL, GObject) @@ -33,6 +36,7 @@ struct _MetaThreadImplClass GObjectClass parent_class; }; +META_EXPORT_TEST MetaThread * meta_thread_impl_get_thread (MetaThreadImpl *thread_impl); GMainContext * meta_thread_impl_get_main_context (MetaThreadImpl *thread_impl); diff --git a/src/backends/native/meta-thread.h b/src/backends/native/meta-thread.h index 2b973bfb0..776f2a92e 100644 --- a/src/backends/native/meta-thread.h +++ b/src/backends/native/meta-thread.h @@ -23,10 +23,12 @@ #include #include "backends/meta-backend-types.h" +#include "core/util-private.h" typedef struct _MetaThreadImpl MetaThreadImpl; #define META_TYPE_THREAD (meta_thread_get_type ()) +META_EXPORT_TEST G_DECLARE_DERIVABLE_TYPE (MetaThread, meta_thread, META, THREAD, GObject) @@ -43,30 +45,37 @@ typedef gpointer (* MetaThreadTaskFunc) (MetaThreadImpl *thread_impl, gpointer user_data, GError **error); +META_EXPORT_TEST void meta_thread_queue_callback (MetaThread *thread, MetaThreadCallback callback, gpointer user_data, GDestroyNotify user_data_destroy); +META_EXPORT_TEST int meta_thread_flush_callbacks (MetaThread *thread); +META_EXPORT_TEST gpointer meta_thread_run_impl_task_sync (MetaThread *thread, MetaThreadTaskFunc func, gpointer user_data, GError **error); +META_EXPORT_TEST GSource * meta_thread_add_source_in_impl (MetaThread *thread, GSourceFunc func, gpointer user_data, GDestroyNotify user_data_destroy); +META_EXPORT_TEST GSource * meta_thread_register_fd_in_impl (MetaThread *thread, int fd, MetaThreadTaskFunc dispatch, gpointer user_data); +META_EXPORT_TEST MetaBackend * meta_thread_get_backend (MetaThread *thread); +META_EXPORT_TEST gboolean meta_thread_is_in_impl_task (MetaThread *thread); gboolean meta_thread_is_waiting_for_impl_task (MetaThread *thread); diff --git a/src/tests/meson.build b/src/tests/meson.build index 95feb9bc6..f18ec47c9 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -370,6 +370,17 @@ if have_native_tests 'suite': 'backends/native', 'sources': [ 'input-capture-tests.c' ], }, + { + 'name': 'thread', + 'suite': 'backends/native', + 'sources': [ + 'native-thread.c', + 'meta-thread-test.c', + 'meta-thread-test.h', + 'meta-thread-impl-test.c', + 'meta-thread-impl-test.h', + ], + }, ] # KMS tests diff --git a/src/tests/meta-thread-impl-test.c b/src/tests/meta-thread-impl-test.c new file mode 100644 index 000000000..db3ec3784 --- /dev/null +++ b/src/tests/meta-thread-impl-test.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#include "config.h" + +#include "tests/meta-thread-impl-test.h" + +struct _MetaThreadImplTest +{ + MetaThreadImpl parent; +}; + +G_DEFINE_TYPE (MetaThreadImplTest, meta_thread_impl_test, + META_TYPE_THREAD_IMPL) + +static void +meta_thread_impl_test_init (MetaThreadImplTest *thread_impl_test) +{ +} + +static void +meta_thread_impl_test_class_init (MetaThreadImplTestClass *klass) +{ +} diff --git a/src/tests/meta-thread-impl-test.h b/src/tests/meta-thread-impl-test.h new file mode 100644 index 000000000..b422236f5 --- /dev/null +++ b/src/tests/meta-thread-impl-test.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef META_THREAD_IMPL_TEST_H +#define META_THREAD_IMPL_TEST_H + +#include "backends/native/meta-thread-impl.h" + +#define META_TYPE_THREAD_IMPL_TEST (meta_thread_impl_test_get_type ()) +G_DECLARE_FINAL_TYPE (MetaThreadImplTest, meta_thread_impl_test, + META, THREAD_IMPL_TEST, + MetaThreadImpl) + +#endif /* META_THREAD_IMPL_TEST_H */ diff --git a/src/tests/meta-thread-test.c b/src/tests/meta-thread-test.c new file mode 100644 index 000000000..c76366aa1 --- /dev/null +++ b/src/tests/meta-thread-test.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#include "config.h" + +#include "tests/meta-thread-test.h" + +#include "backends/native/meta-thread-private.h" +#include "tests/meta-thread-impl-test.h" + +struct _MetaThreadTest +{ + MetaThread parent; +}; + +G_DEFINE_TYPE (MetaThreadTest, meta_thread_test, + META_TYPE_THREAD) + +static void +meta_thread_test_init (MetaThreadTest *thread_test) +{ +} + +static void +meta_thread_test_class_init (MetaThreadTestClass *klass) +{ + MetaThreadClass *thread_class = META_THREAD_CLASS (klass); + + meta_thread_class_register_impl_type (thread_class, META_TYPE_THREAD_IMPL_TEST); +} diff --git a/src/tests/meta-thread-test.h b/src/tests/meta-thread-test.h new file mode 100644 index 000000000..e9f128a01 --- /dev/null +++ b/src/tests/meta-thread-test.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef META_THREAD_TEST_H +#define META_THREAD_TEST_H + +#include "backends/native/meta-thread.h" + +#define META_TYPE_THREAD_TEST (meta_thread_test_get_type ()) +G_DECLARE_FINAL_TYPE (MetaThreadTest, meta_thread_test, + META, THREAD_TEST, + MetaThread) + +#endif /* META_THREAD_TEST_H */ diff --git a/src/tests/native-thread.c b/src/tests/native-thread.c new file mode 100644 index 000000000..f71fef585 --- /dev/null +++ b/src/tests/native-thread.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2021 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include + +#include "backends/native/meta-thread.h" +#include "backends/native/meta-thread-impl.h" +#include "meta-test/meta-context-test.h" +#include "tests/meta-thread-impl-test.h" +#include "tests/meta-thread-test.h" + +static MetaContext *test_context; +static MetaThread *test_thread; + +static gpointer +impl_func (MetaThreadImpl *thread_impl, + gpointer user_data, + GError **error) +{ + gboolean *done = user_data; + + meta_assert_in_thread_impl (meta_thread_impl_get_thread (thread_impl)); + + *done = TRUE; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not a real error"); + + return GINT_TO_POINTER (42); +} + +static void +callback_func (MetaThread *thread, + gpointer user_data) +{ + int *state = user_data; + + meta_assert_not_in_thread_impl (thread); + + g_assert_cmpint (*state, ==, 1); + *state = 2; +} + +static void +user_data_destroy (gpointer user_data) +{ + int *state = user_data; + + meta_assert_not_in_thread_impl (test_thread); + + g_assert_cmpint (*state, ==, 2); + *state = 3; +} + +static gpointer +queue_callback_func (MetaThreadImpl *thread_impl, + gpointer user_data, + GError **error) +{ + int *state = user_data; + + meta_assert_in_thread_impl (meta_thread_impl_get_thread (thread_impl)); + + g_assert_cmpint (*state, ==, 0); + *state = 1; + + meta_thread_queue_callback (meta_thread_impl_get_thread (thread_impl), + callback_func, + user_data, + user_data_destroy); + return GINT_TO_POINTER (TRUE); +} + +typedef struct +{ + int fd; + GMainLoop *loop; + int read_value; + + GSource *source; +} PipeData; + +static gpointer +dispatch_pipe (MetaThreadImpl *thread_impl, + gpointer user_data, + GError **error) +{ + PipeData *pipe_data = user_data; + + meta_assert_in_thread_impl (meta_thread_impl_get_thread (thread_impl)); + + g_assert_cmpint (read (pipe_data->fd, &pipe_data->read_value, + sizeof (pipe_data->read_value)), + ==, + sizeof (pipe_data->read_value)); + g_main_loop_quit (pipe_data->loop); + g_source_destroy (pipe_data->source); + g_source_unref (pipe_data->source); + + return GINT_TO_POINTER (TRUE); +} + +static gpointer +register_fd_func (MetaThreadImpl *thread_impl, + gpointer user_data, + GError **error) +{ + PipeData *pipe_data = user_data; + + pipe_data->source = + meta_thread_register_fd_in_impl (meta_thread_impl_get_thread (thread_impl), + pipe_data->fd, + dispatch_pipe, + pipe_data); + + return GINT_TO_POINTER (TRUE); +} + +typedef struct +{ + GMainLoop *loop; + + int state; + + GSource *source; +} IdleData; + +static gboolean +idle_cb (gpointer user_data) +{ + IdleData *idle_data = user_data; + + meta_assert_in_thread_impl (test_thread); + + if (idle_data->state == 1) + { + idle_data->state = 2; + return G_SOURCE_REMOVE; + } + + g_assert_cmpint (idle_data->state, ==, 0); + idle_data->state = 1; + return G_SOURCE_CONTINUE; +} + +static void +idle_data_destroy (gpointer user_data) +{ + IdleData *idle_data = user_data; + + /* XXX: This can only be checked on kernel type threads. */ + /* meta_assert_in_thread_impl (test_thread); */ + + g_assert_cmpint (idle_data->state, ==, 2); + idle_data->state = 3; + + g_main_loop_quit (idle_data->loop); +} + +static gpointer +add_idle_func (MetaThreadImpl *thread_impl, + gpointer user_data, + GError **error) +{ + IdleData *idle_data = user_data; + GSource *source; + + meta_assert_in_thread_impl (meta_thread_impl_get_thread (thread_impl)); + + source = + meta_thread_add_source_in_impl (meta_thread_impl_get_thread (thread_impl), + idle_cb, + idle_data, + idle_data_destroy); + g_source_unref (source); + + return GINT_TO_POINTER (TRUE); +} + +static void +run_thread_tests (MetaThread *thread) +{ + gboolean done = FALSE; + GError *error = NULL; + gpointer retval; + int state; + int fds[2]; + int buf; + PipeData pipe_data; + IdleData idle_data; + + meta_assert_not_in_thread_impl (thread); + + /* Test that sync tasks run correctly. */ + g_debug ("Test synchronous tasks"); + retval = meta_thread_run_impl_task_sync (thread, impl_func, &done, &error); + g_assert_true (done); + g_assert_nonnull (error); + g_assert_cmpint (GPOINTER_TO_INT (retval), ==, 42); + g_clear_error (&error); + + /* Test that callbacks run correctly. */ + g_debug ("Test callbacks"); + state = 0; + meta_thread_run_impl_task_sync (thread, queue_callback_func, &state, NULL); + g_assert_cmpint (state, ==, 1); + while (g_main_context_iteration (NULL, FALSE)); + g_assert_cmpint (state, ==, 3); + + /* Test callback flushing */ + g_debug ("Test callbacks flushing"); + state = 0; + meta_thread_run_impl_task_sync (thread, queue_callback_func, &state, NULL); + g_assert_cmpint (state, ==, 1); + meta_thread_flush_callbacks (thread); + g_assert_cmpint (state, ==, 3); + + /* Test fd source */ + g_debug ("Test fd source"); + pipe_data = (PipeData) { 0 }; + g_assert_true (g_unix_open_pipe (fds, FD_CLOEXEC, NULL)); + pipe_data.fd = fds[0]; + pipe_data.loop = g_main_loop_new (NULL, FALSE); + meta_thread_run_impl_task_sync (thread, register_fd_func, &pipe_data, NULL); + buf = 100; + g_assert_cmpint (write (fds[1], &buf, sizeof (buf)), ==, sizeof (buf)); + g_main_loop_run (pipe_data.loop); + g_assert_cmpint (pipe_data.read_value, ==, 100); + g_main_loop_unref (pipe_data.loop); + close (fds[0]); + close (fds[1]); + + /* Test idle source */ + g_debug ("Test idle source"); + idle_data = (IdleData) { 0 }; + idle_data.loop = g_main_loop_new (NULL, FALSE); + meta_thread_run_impl_task_sync (thread, add_idle_func, &idle_data, NULL); + g_main_loop_run (idle_data.loop); + g_assert_cmpint (idle_data.state, ==, 3); + g_main_loop_unref (idle_data.loop); +} + +static void +meta_test_thread_user_common (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + MetaThread *thread; + g_autoptr (GError) error = NULL; + + thread = g_initable_new (META_TYPE_THREAD_TEST, + NULL, &error, + "backend", backend, + NULL); + g_object_add_weak_pointer (G_OBJECT (thread), (gpointer *) &thread); + g_assert_nonnull (thread); + g_assert_null (error); + g_assert (meta_thread_get_backend (thread) == backend); + test_thread = thread; + + run_thread_tests (thread); + + g_object_unref (thread); + g_assert_null (thread); + test_thread = NULL; +} + +static void +init_tests (void) +{ + g_test_add_func ("/backends/native/thread/user/common", + meta_test_thread_user_common); +} + +int +main (int argc, + char **argv) +{ + g_autoptr (MetaContext) context = NULL; + + context = meta_create_test_context (META_CONTEXT_TEST_TYPE_HEADLESS, + META_CONTEXT_TEST_FLAG_NO_X11); + g_assert (meta_context_configure (context, &argc, &argv, NULL)); + + init_tests (); + + test_context = context; + return meta_context_test_run_tests (META_CONTEXT_TEST (context), + META_TEST_RUN_FLAG_NONE); +}