#include "clutter-test-utils.h" #include #include #include #include "compositor/meta-plugin-manager.h" #include "core/main-private.h" typedef struct { gpointer dummy_field; } ClutterTestEnvironment; static ClutterTestEnvironment *test_environ = 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; } static const char * test_get_plugin_name (void) { const char *name; name = g_getenv ("MUTTER_TEST_PLUGIN_PATH"); if (name) return name; else return "libdefault"; } static void init_common_pre (void) { const char *display; if (G_UNLIKELY (test_environ != NULL)) g_error ("Attempting to initialize the test suite more than once, " "aborting...\n"); meta_plugin_manager_load (test_get_plugin_name ()); meta_override_x11_display_policy (META_DISPLAY_POLICY_DISABLED); meta_test_init (); display = g_getenv ("DISPLAY"); if (!display || *display == '\0') { g_error ("No DISPLAY environment variable found, but we require a " "DISPLAY set in order to run the conformance test suite.\n" "Skipping all tests.\n"); } /* we explicitly disable the synchronisation to the vertical refresh * rate, and run the master clock using a 60 fps timer instead. */ _clutter_set_sync_to_vblank (FALSE); } static void init_common_post (int *argc, char ***argv) { g_test_init (argc, argv, NULL); g_test_bug_base ("https://bugzilla.gnome.org/show_bug.cgi?id=%s"); /* our global state, accessible from each test unit */ test_environ = g_new0 (ClutterTestEnvironment, 1); meta_start (); } /* * clutter_test_init: * @argc: (inout): number of arguments in @argv * @argv: (inout) (array length=argc) (nullable): array of arguments * * Initializes the Clutter test environment. * * Since: 1.18 */ void clutter_test_init (int *argc, char ***argv) { init_common_pre (); g_assert (clutter_init (NULL, NULL) == CLUTTER_INIT_SUCCESS); init_common_post (argc, argv); } void clutter_test_init_with_args (int *argc, char ***argv, const char *parameter_string, GOptionEntry *entries, const char *translation_domain) { init_common_pre (); g_assert (clutter_init_with_args (argc, argv, parameter_string, entries, translation_domain, NULL) == CLUTTER_INIT_SUCCESS); init_common_post (argc, argv); } /** * clutter_test_get_stage: * * Retrieves the #ClutterStage used for testing. * * Return value: (transfer none): the stage used for testing * * Since: 1.18 */ ClutterActor * clutter_test_get_stage (void) { MetaBackend *backend = meta_get_backend (); return meta_backend_get_stage (backend); } 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() * * Since: 1.18 */ 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() * * Since: 1.18 */ 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() * * Since: 1.18 */ 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 * * Since: 1.18 */ int clutter_test_run (void) { int res; g_assert (test_environ != NULL); res = g_test_run (); g_free (test_environ); return res; } void clutter_test_main (void) { meta_run_main_loop (); } void clutter_test_quit (void) { meta_quit (META_EXIT_SUCCESS); } 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 * * Since: 1.18 */ 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 * * Since: 1.18 */ 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; }