mirror of
https://github.com/brl/mutter.git
synced 2024-11-27 10:30:47 -05:00
f57c39f511
This avoids test failure happening due to Xwayland getting the CPU time instead of the tests themself, causing failures e.g. due to missing frames. https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1555
527 lines
13 KiB
C
527 lines
13 KiB
C
#include "clutter-test-utils.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <glib-object.h>
|
|
#include <clutter/clutter.h>
|
|
|
|
#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;
|
|
}
|