#include "clutter-test-utils.h" #include #include #include typedef struct { ClutterActor *stage; guint no_display : 1; } 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; } /* * 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) { gboolean no_display = FALSE; if (G_UNLIKELY (test_environ != NULL)) g_error ("Attempting to initialize the test suite more than once, " "aborting...\n"); #ifdef CLUTTER_WINDOWING_X11 /* on X11 backends we need the DISPLAY environment set. * * check_windowing_backend() will pre-initialize the Clutter * backend object. */ if (clutter_check_windowing_backend (CLUTTER_WINDOWING_X11)) { const char *display = g_getenv ("DISPLAY"); if (display == NULL || *display == '\0') { g_test_message ("No DISPLAY environment variable found, but we require a " "DISPLAY set in order to run the conformance test suite.\n" "Skipping all tests.\n"); no_display = TRUE; goto out; } } #endif /* 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); /* perform the actual initialization */ g_assert (clutter_init (NULL, NULL) == CLUTTER_INIT_SUCCESS); out: 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); test_environ->no_display = no_display; } /** * 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) { g_assert (test_environ != NULL); if (test_environ->stage == NULL) { /* create a stage, and ensure that it goes away at the end */ test_environ->stage = clutter_stage_new (); clutter_actor_set_name (test_environ->stage, "Test Stage"); g_object_add_weak_pointer (G_OBJECT (test_environ->stage), (gpointer *) &test_environ->stage); } return test_environ->stage; } typedef struct { gpointer test_func; gpointer test_data; GDestroyNotify test_notify; } ClutterTestData; static void clutter_test_func_wrapper (gconstpointer data_) { const ClutterTestData *data = data_; g_test_log_set_fatal_handler (log_func, NULL); /* ensure that the previous test state has been cleaned up */ g_assert_null (test_environ->stage); if (test_environ->no_display) { g_test_skip ("No DISPLAY set"); goto out; } 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 (); } out: if (data->test_notify != NULL) data->test_notify (data->test_data); if (test_environ->stage != NULL) { clutter_actor_destroy (test_environ->stage); g_assert_null (test_environ->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; } 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; }