#include "clutter-test-utils.h" #include #include #include #include "backends/meta-monitor-manager-private.h" #include "backends/meta-virtual-monitor.h" #include "compositor/meta-plugin-manager.h" #include "core/meta-context-private.h" #include "tests/meta-test-utils.h" typedef struct { MetaContext *context; } ClutterTestEnvironment; static ClutterTestEnvironment *test_environ = NULL; static GMainLoop *clutter_test_main_loop = NULL; #define DBUS_NAME_WARNING "Lost or failed to acquire name" static gboolean log_func (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { if ((log_level & G_LOG_LEVEL_WARNING) && g_strcmp0 (log_domain, "mutter") == 0 && g_str_has_prefix (message, DBUS_NAME_WARNING)) return FALSE; return TRUE; } /* * clutter_test_init: * @argc: (inout): number of arguments in @argv * @argv: (inout) (array length=argc) (nullable): array of arguments * * Initializes the Clutter test environment. */ void clutter_test_init (int *argc, char ***argv) { MetaContext *context; 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)); g_assert (meta_context_setup (context, NULL)); test_environ = g_new0 (ClutterTestEnvironment, 1); test_environ->context = context; g_assert (meta_context_start (context, NULL)); clutter_test_main_loop = g_main_loop_new (NULL, FALSE); } /** * clutter_test_get_stage: * * Retrieves the #ClutterStage used for testing. * * Return value: (transfer none): the stage used for testing */ ClutterActor * clutter_test_get_stage (void) { MetaBackend *backend = meta_get_backend (); return meta_backend_get_stage (backend); } void clutter_test_flush_input (void) { meta_flush_input (test_environ->context); } typedef struct { gpointer test_func; gpointer test_data; GDestroyNotify test_notify; } ClutterTestData; static gboolean list_equal_unsorted (GList *list_a, GList *list_b) { GList *l_a; GList *l_b; for (l_a = list_a, l_b = list_b; l_a && l_b; l_a = l_a->next, l_b = l_b->next) { if (l_a->data != l_b->data) return FALSE; } return !l_a && !l_b; } static void clutter_test_func_wrapper (gconstpointer data_) { const ClutterTestData *data = data_; ClutterActor *stage; GList *pre_stage_children; GList *post_stage_children; g_test_log_set_fatal_handler (log_func, NULL); /* ensure that the previous test state has been cleaned up */ stage = clutter_test_get_stage (); clutter_actor_hide (stage); pre_stage_children = clutter_actor_get_children (stage); if (data->test_data != NULL) { GTestDataFunc test_func = data->test_func; test_func (data->test_data); } else { GTestFunc test_func = data->test_func; test_func (); } if (data->test_notify != NULL) data->test_notify (data->test_data); post_stage_children = clutter_actor_get_children (stage); g_assert_true (list_equal_unsorted (pre_stage_children, post_stage_children)); g_list_free (pre_stage_children); g_list_free (post_stage_children); clutter_actor_hide (stage); } /** * clutter_test_add: (skip) * @test_path: unique path for identifying the test * @test_func: function containing the test * * Adds a test unit to the Clutter test environment. * * See also: g_test_add() */ void clutter_test_add (const char *test_path, GTestFunc test_func) { clutter_test_add_data_full (test_path, (GTestDataFunc) test_func, NULL, NULL); } /** * clutter_test_add_data: (skip) * @test_path: unique path for identifying the test * @test_func: function containing the test * @test_data: data to pass to the test function * * Adds a test unit to the Clutter test environment. * * See also: g_test_add_data_func() */ void clutter_test_add_data (const char *test_path, GTestDataFunc test_func, gpointer test_data) { clutter_test_add_data_full (test_path, test_func, test_data, NULL); } /** * clutter_test_add_data_full: * @test_path: unique path for identifying the test * @test_func: (scope notified): function containing the test * @test_data: (closure): data to pass to the test function * @test_notify: function called when the test function ends * * Adds a test unit to the Clutter test environment. * * See also: g_test_add_data_func_full() */ void clutter_test_add_data_full (const char *test_path, GTestDataFunc test_func, gpointer test_data, GDestroyNotify test_notify) { ClutterTestData *data; g_return_if_fail (test_path != NULL); g_return_if_fail (test_func != NULL); g_assert (test_environ != NULL); data = g_new (ClutterTestData, 1); data->test_func = test_func; data->test_data = test_data; data->test_notify = test_notify; g_test_add_data_func_full (test_path, data, clutter_test_func_wrapper, g_free); } /** * clutter_test_run: * * Runs the test suite using the units added by calling * clutter_test_add(). * * The typical test suite is composed of a list of functions * called by clutter_test_run(), for instance: * * |[ * static void unit_foo (void) { ... } * * static void unit_bar (void) { ... } * * static void unit_baz (void) { ... } * * int * main (int argc, char *argv[]) * { * clutter_test_init (&argc, &argv); * * clutter_test_add ("/unit/foo", unit_foo); * clutter_test_add ("/unit/bar", unit_bar); * clutter_test_add ("/unit/baz", unit_baz); * * return clutter_test_run (); * } * ]| * * Return value: the exit code for the test suite */ int clutter_test_run (void) { MetaBackend *backend = meta_context_get_backend (test_environ->context); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaVirtualMonitor *virtual_monitor; g_autoptr (MetaVirtualMonitorInfo) monitor_info = NULL; g_autoptr (GError) error = NULL; int res; monitor_info = meta_virtual_monitor_info_new (800, 600, 10.0, "MetaTestVendor", "ClutterTestMonitor", "0x123"); virtual_monitor = meta_monitor_manager_create_virtual_monitor (monitor_manager, monitor_info, &error); if (!virtual_monitor) g_error ("Failed to create virtual monitor: %s", error->message); meta_monitor_manager_reload (monitor_manager); res = g_test_run (); g_object_unref (virtual_monitor); g_clear_object (&test_environ->context); g_free (test_environ); return res; } void clutter_test_main (void) { g_assert_nonnull (clutter_test_main_loop); g_main_loop_run (clutter_test_main_loop); } void clutter_test_quit (void) { g_assert_nonnull (clutter_test_main_loop); g_main_loop_quit (clutter_test_main_loop); } typedef struct { ClutterActor *stage; graphene_point_t point; gpointer result; guint check_actor : 1; guint check_color : 1; guint was_painted : 1; } ValidateData; static gboolean validate_stage (gpointer data_) { ValidateData *data = data_; if (data->check_actor) { data->result = clutter_stage_get_actor_at_pos (CLUTTER_STAGE (data->stage), CLUTTER_PICK_ALL, data->point.x, data->point.y); } if (data->check_color) { data->result = clutter_stage_read_pixels (CLUTTER_STAGE (data->stage), data->point.x, data->point.y, 1, 1); } if (!g_test_verbose ()) { clutter_actor_hide (data->stage); data->was_painted = TRUE; } return G_SOURCE_REMOVE; } static gboolean on_key_press_event (ClutterActor *stage, ClutterEvent *event, gpointer data_) { ValidateData *data = data_; if (data->stage == stage && clutter_event_get_key_symbol (event) == CLUTTER_KEY_Escape) { clutter_actor_hide (stage); data->was_painted = TRUE; } return CLUTTER_EVENT_PROPAGATE; } /** * clutter_test_check_actor_at_point: * @stage: a #ClutterStage * @point: coordinates to check * @actor: the expected actor at the given coordinates * @result: (out) (nullable): actor at the coordinates * * Checks the given coordinates of the @stage and compares the * actor found there with the given @actor. * * Returns: %TRUE if the actor at the given coordinates matches */ gboolean clutter_test_check_actor_at_point (ClutterActor *stage, const graphene_point_t *point, ClutterActor *actor, ClutterActor **result) { ValidateData *data; gulong press_id = 0; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); g_return_val_if_fail (point != NULL, FALSE); g_return_val_if_fail (CLUTTER_IS_ACTOR (stage), FALSE); g_return_val_if_fail (result != NULL, FALSE); data = g_new0 (ValidateData, 1); data->stage = stage; data->point = *point; data->check_actor = TRUE; if (g_test_verbose ()) { g_printerr ("Press ESC to close the stage and resume the test\n"); press_id = g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press_event), data); } clutter_actor_show (stage); clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT, validate_stage, data, NULL); while (!data->was_painted) g_main_context_iteration (NULL, TRUE); *result = data->result; g_clear_signal_handler (&press_id, stage); g_free (data); return *result == actor; } /** * clutter_test_check_color_at_point: * @stage: a #ClutterStage * @point: coordinates to check * @color: expected color * @result: (out caller-allocates): color at the given coordinates * * Checks the color at the given coordinates on @stage, and matches * it with the red, green, and blue channels of @color. The alpha * component of @color and @result is ignored. * * Returns: %TRUE if the colors match */ gboolean clutter_test_check_color_at_point (ClutterActor *stage, const graphene_point_t *point, const ClutterColor *color, ClutterColor *result) { ValidateData *data; gboolean retval; guint8 *buffer; gulong press_id = 0; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); g_return_val_if_fail (point != NULL, FALSE); g_return_val_if_fail (color != NULL, FALSE); g_return_val_if_fail (result != NULL, FALSE); data = g_new0 (ValidateData, 1); data->stage = stage; data->point = *point; data->check_color = TRUE; if (g_test_verbose ()) { g_printerr ("Press ESC to close the stage and resume the test\n"); press_id = g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press_event), data); } clutter_actor_show (stage); clutter_threads_add_repaint_func_full (CLUTTER_REPAINT_FLAGS_POST_PAINT, validate_stage, data, NULL); while (!data->was_painted) g_main_context_iteration (NULL, TRUE); g_clear_signal_handler (&press_id, stage); buffer = data->result; clutter_color_init (result, buffer[0], buffer[1], buffer[2], 255); /* we only check the color channels, so we can't use clutter_color_equal() */ retval = buffer[0] == color->red && buffer[1] == color->green && buffer[2] == color->blue; g_free (data->result); g_free (data); return retval; }