mutter/src/tests/clutter-test-utils.c
Jonas Ådahl 007d27fa40 tests/conform: Use the clutter stage from mutter
The tests created their own stage, which caused various issues. Lets use
the one from mutter instead.

https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1289
2020-06-05 21:39:27 +00:00

463 lines
11 KiB
C

#include "clutter-test-utils.h"
#include <stdlib.h>
#include <glib-object.h>
#include <clutter/clutter.h>
typedef struct {
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)
{
MetaBackend *backend = meta_get_backend ();
return meta_backend_get_stage (backend);
}
typedef struct {
gpointer test_func;
gpointer test_data;
GDestroyNotify test_notify;
} ClutterTestData;
static void
clutter_test_func_wrapper (gconstpointer data_)
{
const ClutterTestData *data = data_;
ClutterActor *stage;
g_test_log_set_fatal_handler (log_func, NULL);
/* ensure that the previous test state has been cleaned up */
stage = clutter_test_get_stage ();
g_assert_false (clutter_actor_is_mapped (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);
clutter_actor_remove_all_children (stage);
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;
}
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;
}