glib/gio/tests/actions.c
Philip Withnall dbe4531e86 gactiongroupexporter: Validate actions activated or changed over D-Bus
The action name, parameter and new state are all controlled by an
external process, so can’t be trusted. Ensure they are validated before
being passed to functions which assert that they are correctly typed and
extant.

Add unit tests.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>

Helps: #1904
2022-12-09 10:45:21 +00:00

1418 lines
48 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2010, 2011, 2013, 2014 Codethink Limited
* Copyright © 2010, 2011, 2012, 2013, 2015 Red Hat, Inc.
* Copyright © 2012 Pavel Vasin
* Copyright © 2022 Endless OS Foundation, LLC
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors: Ryan Lortie <desrt@desrt.ca>
*/
#include <gio/gio.h>
#include <stdlib.h>
#include <string.h>
#include "gdbus-sessionbus.h"
typedef struct
{
GVariant *params;
gboolean did_run;
} Activation;
static void
activate (GAction *action,
GVariant *parameter,
gpointer user_data)
{
Activation *activation = user_data;
if (parameter)
activation->params = g_variant_ref (parameter);
else
activation->params = NULL;
activation->did_run = TRUE;
}
static void
test_basic (void)
{
Activation a = { 0, };
GSimpleAction *action;
gchar *name;
GVariantType *parameter_type;
gboolean enabled;
GVariantType *state_type;
GVariant *state;
action = g_simple_action_new ("foo", NULL);
g_assert_true (g_action_get_enabled (G_ACTION (action)));
g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
g_assert_null (g_action_get_state_type (G_ACTION (action)));
g_assert_null (g_action_get_state_hint (G_ACTION (action)));
g_assert_null (g_action_get_state (G_ACTION (action)));
g_object_get (action,
"name", &name,
"parameter-type", &parameter_type,
"enabled", &enabled,
"state-type", &state_type,
"state", &state,
NULL);
g_assert_cmpstr (name, ==, "foo");
g_assert_null (parameter_type);
g_assert_true (enabled);
g_assert_null (state_type);
g_assert_null (state);
g_free (name);
g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
g_assert_false (a.did_run);
g_action_activate (G_ACTION (action), NULL);
g_assert_true (a.did_run);
a.did_run = FALSE;
g_simple_action_set_enabled (action, FALSE);
g_action_activate (G_ACTION (action), NULL);
g_assert_false (a.did_run);
if (g_test_undefined ())
{
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion*g_variant_is_of_type*failed*");
g_action_activate (G_ACTION (action), g_variant_new_string ("xxx"));
g_test_assert_expected_messages ();
}
g_object_unref (action);
g_assert_false (a.did_run);
action = g_simple_action_new ("foo", G_VARIANT_TYPE_STRING);
g_assert_true (g_action_get_enabled (G_ACTION (action)));
g_assert_true (g_variant_type_equal (g_action_get_parameter_type (G_ACTION (action)), G_VARIANT_TYPE_STRING));
g_assert_null (g_action_get_state_type (G_ACTION (action)));
g_assert_null (g_action_get_state_hint (G_ACTION (action)));
g_assert_null (g_action_get_state (G_ACTION (action)));
g_signal_connect (action, "activate", G_CALLBACK (activate), &a);
g_assert_false (a.did_run);
g_action_activate (G_ACTION (action), g_variant_new_string ("Hello world"));
g_assert_true (a.did_run);
g_assert_cmpstr (g_variant_get_string (a.params, NULL), ==, "Hello world");
g_variant_unref (a.params);
a.did_run = FALSE;
if (g_test_undefined ())
{
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion*!= NULL*failed*");
g_action_activate (G_ACTION (action), NULL);
g_test_assert_expected_messages ();
}
g_object_unref (action);
g_assert_false (a.did_run);
}
static void
test_name (void)
{
g_assert_false (g_action_name_is_valid (""));
g_assert_false (g_action_name_is_valid ("("));
g_assert_false (g_action_name_is_valid ("%abc"));
g_assert_false (g_action_name_is_valid ("$x1"));
g_assert_true (g_action_name_is_valid ("abc.def"));
g_assert_true (g_action_name_is_valid ("ABC-DEF"));
}
static gboolean
strv_strv_cmp (const gchar * const *a,
const gchar * const *b)
{
guint n;
for (n = 0; a[n] != NULL; n++)
{
if (!g_strv_contains (b, a[n]))
return FALSE;
}
for (n = 0; b[n] != NULL; n++)
{
if (!g_strv_contains (a, b[n]))
return FALSE;
}
return TRUE;
}
static gboolean
strv_set_equal (const gchar * const *strv, ...)
{
guint count;
va_list list;
const gchar *str;
gboolean res;
res = TRUE;
count = 0;
va_start (list, strv);
while (1)
{
str = va_arg (list, const gchar *);
if (str == NULL)
break;
if (!g_strv_contains (strv, str))
{
res = FALSE;
break;
}
count++;
}
va_end (list);
if (res)
res = g_strv_length ((gchar**)strv) == count;
return res;
}
static void
ensure_state (GActionGroup *group,
const gchar *action_name,
const gchar *expected)
{
GVariant *value;
gchar *printed;
value = g_action_group_get_action_state (group, action_name);
printed = g_variant_print (value, TRUE);
g_variant_unref (value);
g_assert_cmpstr (printed, ==, expected);
g_free (printed);
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static void
test_simple_group (void)
{
GSimpleActionGroup *group;
Activation a = { 0, };
GSimpleAction *simple;
GAction *action;
gchar **actions;
GVariant *state;
simple = g_simple_action_new ("foo", NULL);
g_signal_connect (simple, "activate", G_CALLBACK (activate), &a);
g_assert_false (a.did_run);
g_action_activate (G_ACTION (simple), NULL);
g_assert_true (a.did_run);
a.did_run = FALSE;
group = g_simple_action_group_new ();
g_simple_action_group_insert (group, G_ACTION (simple));
g_object_unref (simple);
g_assert_false (a.did_run);
g_action_group_activate_action (G_ACTION_GROUP (group), "foo", NULL);
g_assert_true (a.did_run);
simple = g_simple_action_new_stateful ("bar", G_VARIANT_TYPE_STRING, g_variant_new_string ("hihi"));
g_simple_action_group_insert (group, G_ACTION (simple));
g_object_unref (simple);
g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "foo"));
g_assert_true (g_action_group_has_action (G_ACTION_GROUP (group), "bar"));
g_assert_false (g_action_group_has_action (G_ACTION_GROUP (group), "baz"));
actions = g_action_group_list_actions (G_ACTION_GROUP (group));
g_assert_cmpint (g_strv_length (actions), ==, 2);
g_assert_true (strv_set_equal ((const gchar * const *) actions, "foo", "bar", NULL));
g_strfreev (actions);
g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "foo"));
g_assert_true (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
g_assert_null (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "foo"));
g_assert_true (g_variant_type_equal (g_action_group_get_action_parameter_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
g_assert_null (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "foo"));
g_assert_true (g_variant_type_equal (g_action_group_get_action_state_type (G_ACTION_GROUP (group), "bar"), G_VARIANT_TYPE_STRING));
g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "foo"));
g_assert_null (g_action_group_get_action_state_hint (G_ACTION_GROUP (group), "bar"));
g_assert_null (g_action_group_get_action_state (G_ACTION_GROUP (group), "foo"));
state = g_action_group_get_action_state (G_ACTION_GROUP (group), "bar");
g_assert_true (g_variant_type_equal (g_variant_get_type (state), G_VARIANT_TYPE_STRING));
g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
g_variant_unref (state);
g_action_group_change_action_state (G_ACTION_GROUP (group), "bar", g_variant_new_string ("boo"));
state = g_action_group_get_action_state (G_ACTION_GROUP (group), "bar");
g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "boo");
g_variant_unref (state);
action = g_simple_action_group_lookup (group, "bar");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
g_assert_false (g_action_group_get_action_enabled (G_ACTION_GROUP (group), "bar"));
g_simple_action_group_remove (group, "bar");
action = g_simple_action_group_lookup (group, "foo");
g_assert_cmpstr (g_action_get_name (action), ==, "foo");
action = g_simple_action_group_lookup (group, "bar");
g_assert_null (action);
simple = g_simple_action_new ("foo", NULL);
g_simple_action_group_insert (group, G_ACTION (simple));
g_object_unref (simple);
a.did_run = FALSE;
g_object_unref (group);
g_assert_false (a.did_run);
}
G_GNUC_END_IGNORE_DEPRECATIONS
static void
test_stateful (void)
{
GSimpleAction *action;
GVariant *state;
action = g_simple_action_new_stateful ("foo", NULL, g_variant_new_string ("hihi"));
g_assert_true (g_action_get_enabled (G_ACTION (action)));
g_assert_null (g_action_get_parameter_type (G_ACTION (action)));
g_assert_null (g_action_get_state_hint (G_ACTION (action)));
g_assert_true (g_variant_type_equal (g_action_get_state_type (G_ACTION (action)),
G_VARIANT_TYPE_STRING));
state = g_action_get_state (G_ACTION (action));
g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hihi");
g_variant_unref (state);
if (g_test_undefined ())
{
GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123));
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion*g_variant_is_of_type*failed*");
g_simple_action_set_state (action, new_state);
g_test_assert_expected_messages ();
g_variant_unref (new_state);
}
g_simple_action_set_state (action, g_variant_new_string ("hello"));
state = g_action_get_state (G_ACTION (action));
g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "hello");
g_variant_unref (state);
g_object_unref (action);
action = g_simple_action_new ("foo", NULL);
if (g_test_undefined ())
{
GVariant *new_state = g_variant_ref_sink (g_variant_new_int32 (123));
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion*!= NULL*failed*");
g_simple_action_set_state (action, new_state);
g_test_assert_expected_messages ();
g_variant_unref (new_state);
}
g_object_unref (action);
}
static void
test_default_activate (void)
{
GSimpleAction *action;
GVariant *state;
/* Test changing state via activation with parameter */
action = g_simple_action_new_stateful ("foo", G_VARIANT_TYPE_STRING, g_variant_new_string ("hihi"));
g_action_activate (G_ACTION (action), g_variant_new_string ("bye"));
state = g_action_get_state (G_ACTION (action));
g_assert_cmpstr (g_variant_get_string (state, NULL), ==, "bye");
g_variant_unref (state);
g_object_unref (action);
/* Test toggling a boolean action via activation with no parameter */
action = g_simple_action_new_stateful ("foo", NULL, g_variant_new_boolean (FALSE));
g_action_activate (G_ACTION (action), NULL);
state = g_action_get_state (G_ACTION (action));
g_assert_true (g_variant_get_boolean (state));
g_variant_unref (state);
/* and back again */
g_action_activate (G_ACTION (action), NULL);
state = g_action_get_state (G_ACTION (action));
g_assert_false (g_variant_get_boolean (state));
g_variant_unref (state);
g_object_unref (action);
}
static gboolean foo_activated = FALSE;
static gboolean bar_activated = FALSE;
static void
activate_foo (GSimpleAction *simple,
GVariant *parameter,
gpointer user_data)
{
g_assert_true (user_data == GINT_TO_POINTER (123));
g_assert_null (parameter);
foo_activated = TRUE;
}
static void
activate_bar (GSimpleAction *simple,
GVariant *parameter,
gpointer user_data)
{
g_assert_true (user_data == GINT_TO_POINTER (123));
g_assert_cmpstr (g_variant_get_string (parameter, NULL), ==, "param");
bar_activated = TRUE;
}
static void
change_volume_state (GSimpleAction *action,
GVariant *value,
gpointer user_data)
{
gint requested;
requested = g_variant_get_int32 (value);
/* Volume only goes from 0 to 10 */
if (0 <= requested && requested <= 10)
g_simple_action_set_state (action, value);
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static void
test_entries (void)
{
const GActionEntry entries[] = {
{ "foo", activate_foo, NULL, NULL, NULL, { 0 } },
{ "bar", activate_bar, "s", NULL, NULL, { 0 } },
{ "toggle", NULL, NULL, "false", NULL, { 0 } },
{ "volume", NULL, NULL, "0", change_volume_state, { 0 } },
};
GSimpleActionGroup *actions;
GVariant *state;
actions = g_simple_action_group_new ();
g_simple_action_group_add_entries (actions, entries,
G_N_ELEMENTS (entries),
GINT_TO_POINTER (123));
g_assert_false (foo_activated);
g_action_group_activate_action (G_ACTION_GROUP (actions), "foo", NULL);
g_assert_true (foo_activated);
foo_activated = FALSE;
g_assert_false (bar_activated);
g_action_group_activate_action (G_ACTION_GROUP (actions), "bar",
g_variant_new_string ("param"));
g_assert_true (bar_activated);
g_assert_false (foo_activated);
if (g_test_undefined ())
{
const GActionEntry bad_type = {
"bad-type", NULL, "ss", NULL, NULL, { 0 }
};
const GActionEntry bad_state = {
"bad-state", NULL, NULL, "flse", NULL, { 0 }
};
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*not a valid GVariant type string*");
g_simple_action_group_add_entries (actions, &bad_type, 1, NULL);
g_test_assert_expected_messages ();
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*could not parse*");
g_simple_action_group_add_entries (actions, &bad_state, 1, NULL);
g_test_assert_expected_messages ();
}
state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
g_assert_cmpint (g_variant_get_int32 (state), ==, 0);
g_variant_unref (state);
/* should change */
g_action_group_change_action_state (G_ACTION_GROUP (actions), "volume",
g_variant_new_int32 (7));
state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
g_variant_unref (state);
/* should not change */
g_action_group_change_action_state (G_ACTION_GROUP (actions), "volume",
g_variant_new_int32 (11));
state = g_action_group_get_action_state (G_ACTION_GROUP (actions), "volume");
g_assert_cmpint (g_variant_get_int32 (state), ==, 7);
g_variant_unref (state);
g_object_unref (actions);
}
G_GNUC_END_IGNORE_DEPRECATIONS
static void
test_parse_detailed (void)
{
struct {
const gchar *detailed;
const gchar *expected_name;
const gchar *expected_target;
const gchar *expected_error;
const gchar *detailed_roundtrip;
} testcases[] = {
{ "abc", "abc", NULL, NULL, "abc" },
{ " abc", NULL, NULL, "invalid format", NULL },
{ " abc", NULL, NULL, "invalid format", NULL },
{ "abc:", NULL, NULL, "invalid format", NULL },
{ ":abc", NULL, NULL, "invalid format", NULL },
{ "abc(", NULL, NULL, "invalid format", NULL },
{ "abc)", NULL, NULL, "invalid format", NULL },
{ "(abc", NULL, NULL, "invalid format", NULL },
{ ")abc", NULL, NULL, "invalid format", NULL },
{ "abc::xyz", "abc", "'xyz'", NULL, "abc::xyz" },
{ "abc('xyz')", "abc", "'xyz'", NULL, "abc::xyz" },
{ "abc(42)", "abc", "42", NULL, "abc(42)" },
{ "abc(int32 42)", "abc", "42", NULL, "abc(42)" },
{ "abc(@i 42)", "abc", "42", NULL, "abc(42)" },
{ "abc (42)", NULL, NULL, "invalid format", NULL },
{ "abc(42abc)", NULL, NULL, "invalid character in number", NULL },
{ "abc(42, 4)", "abc", "(42, 4)", "expected end of input", NULL },
{ "abc(42,)", "abc", "(42,)", "expected end of input", NULL }
};
gsize i;
for (i = 0; i < G_N_ELEMENTS (testcases); i++)
{
GError *error = NULL;
GVariant *target;
gboolean success;
gchar *name;
success = g_action_parse_detailed_name (testcases[i].detailed, &name, &target, &error);
g_assert_true (success == (error == NULL));
if (success && testcases[i].expected_error)
g_error ("Unexpected success on '%s'. Expected error containing '%s'",
testcases[i].detailed, testcases[i].expected_error);
if (!success && !testcases[i].expected_error)
g_error ("Unexpected failure on '%s': %s", testcases[i].detailed, error->message);
if (!success)
{
if (!strstr (error->message, testcases[i].expected_error))
g_error ("Failure message '%s' for string '%s' did not contained expected substring '%s'",
error->message, testcases[i].detailed, testcases[i].expected_error);
g_error_free (error);
continue;
}
g_assert_cmpstr (name, ==, testcases[i].expected_name);
g_assert_true ((target == NULL) == (testcases[i].expected_target == NULL));
if (success)
{
gchar *detailed;
detailed = g_action_print_detailed_name (name, target);
g_assert_cmpstr (detailed, ==, testcases[i].detailed_roundtrip);
g_free (detailed);
}
if (target)
{
GVariant *expected;
expected = g_variant_parse (NULL, testcases[i].expected_target, NULL, NULL, NULL);
g_assert_true (expected);
g_assert_cmpvariant (expected, target);
g_variant_unref (expected);
g_variant_unref (target);
}
g_free (name);
}
}
GHashTable *activation_counts;
static void
count_activation (const gchar *action)
{
gint count;
if (activation_counts == NULL)
activation_counts = g_hash_table_new (g_str_hash, g_str_equal);
count = GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
count++;
g_hash_table_insert (activation_counts, (gpointer)action, GINT_TO_POINTER (count));
g_main_context_wakeup (NULL);
}
static gint
activation_count (const gchar *action)
{
if (activation_counts == NULL)
return 0;
return GPOINTER_TO_INT (g_hash_table_lookup (activation_counts, action));
}
static void
activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
count_activation (g_action_get_name (G_ACTION (action)));
}
static void
activate_toggle (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
GVariant *old_state, *new_state;
count_activation (g_action_get_name (G_ACTION (action)));
old_state = g_action_get_state (G_ACTION (action));
new_state = g_variant_new_boolean (!g_variant_get_boolean (old_state));
g_simple_action_set_state (action, new_state);
g_variant_unref (old_state);
}
static void
activate_radio (GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
GVariant *new_state;
count_activation (g_action_get_name (G_ACTION (action)));
new_state = g_variant_new_string (g_variant_get_string (parameter, NULL));
g_simple_action_set_state (action, new_state);
}
static gboolean
compare_action_groups (GActionGroup *a, GActionGroup *b)
{
gchar **alist;
gchar **blist;
gint i;
gboolean equal;
gboolean ares, bres;
gboolean aenabled, benabled;
const GVariantType *aparameter_type, *bparameter_type;
const GVariantType *astate_type, *bstate_type;
GVariant *astate_hint, *bstate_hint;
GVariant *astate, *bstate;
alist = g_action_group_list_actions (a);
blist = g_action_group_list_actions (b);
equal = strv_strv_cmp ((const gchar * const *) alist, (const gchar * const *) blist);
for (i = 0; equal && alist[i]; i++)
{
ares = g_action_group_query_action (a, alist[i], &aenabled, &aparameter_type, &astate_type, &astate_hint, &astate);
bres = g_action_group_query_action (b, alist[i], &benabled, &bparameter_type, &bstate_type, &bstate_hint, &bstate);
if (ares && bres)
{
equal = equal && (aenabled == benabled);
equal = equal && ((!aparameter_type && !bparameter_type) || g_variant_type_equal (aparameter_type, bparameter_type));
equal = equal && ((!astate_type && !bstate_type) || g_variant_type_equal (astate_type, bstate_type));
equal = equal && ((!astate_hint && !bstate_hint) || g_variant_equal (astate_hint, bstate_hint));
equal = equal && ((!astate && !bstate) || g_variant_equal (astate, bstate));
if (astate_hint)
g_variant_unref (astate_hint);
if (bstate_hint)
g_variant_unref (bstate_hint);
if (astate)
g_variant_unref (astate);
if (bstate)
g_variant_unref (bstate);
}
else
equal = FALSE;
}
g_strfreev (alist);
g_strfreev (blist);
return equal;
}
static gboolean
timeout_cb (gpointer user_data)
{
gboolean *timed_out = user_data;
g_assert_false (*timed_out);
*timed_out = TRUE;
g_main_context_wakeup (NULL);
return G_SOURCE_REMOVE;
}
static GActionEntry exported_entries[] = {
{ "undo", activate_action, NULL, NULL, NULL, { 0 } },
{ "redo", activate_action, NULL, NULL, NULL, { 0 } },
{ "cut", activate_action, NULL, NULL, NULL, { 0 } },
{ "copy", activate_action, NULL, NULL, NULL, { 0 } },
{ "paste", activate_action, NULL, NULL, NULL, { 0 } },
{ "bold", activate_toggle, NULL, "true", NULL, { 0 } },
{ "lang", activate_radio, "s", "'latin'", NULL, { 0 } },
};
static void
async_result_cb (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
GAsyncResult **result_out = user_data;
g_assert_null (*result_out);
*result_out = g_object_ref (res);
g_main_context_wakeup (NULL);
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static void
action_added_removed_cb (GActionGroup *action_group,
char *action_name,
gpointer user_data)
{
guint *counter = user_data;
*counter = *counter + 1;
g_main_context_wakeup (NULL);
}
static void
action_enabled_changed_cb (GActionGroup *action_group,
char *action_name,
gboolean enabled,
gpointer user_data)
{
guint *counter = user_data;
*counter = *counter + 1;
g_main_context_wakeup (NULL);
}
static void
action_state_changed_cb (GActionGroup *action_group,
char *action_name,
GVariant *value,
gpointer user_data)
{
guint *counter = user_data;
*counter = *counter + 1;
g_main_context_wakeup (NULL);
}
static void
test_dbus_export (void)
{
GDBusConnection *bus;
GSimpleActionGroup *group;
GDBusActionGroup *proxy;
GSimpleAction *action;
GError *error = NULL;
GVariant *v;
guint id;
gchar **actions;
guint n_actions_added = 0, n_actions_enabled_changed = 0, n_actions_removed = 0, n_actions_state_changed = 0;
gulong added_signal_id, enabled_changed_signal_id, removed_signal_id, state_changed_signal_id;
gboolean enabled;
gchar *param;
GVariantIter *iter;
GAsyncResult *async_result = NULL;
session_bus_up ();
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
group = g_simple_action_group_new ();
g_simple_action_group_add_entries (group,
exported_entries,
G_N_ELEMENTS (exported_entries),
NULL);
id = g_dbus_connection_export_action_group (bus, "/", G_ACTION_GROUP (group), &error);
g_assert_no_error (error);
proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/");
added_signal_id = g_signal_connect (proxy, "action-added", G_CALLBACK (action_added_removed_cb), &n_actions_added);
enabled_changed_signal_id = g_signal_connect (proxy, "action-enabled-changed", G_CALLBACK (action_enabled_changed_cb), &n_actions_enabled_changed);
removed_signal_id = g_signal_connect (proxy, "action-removed", G_CALLBACK (action_added_removed_cb), &n_actions_removed);
state_changed_signal_id = g_signal_connect (proxy, "action-state-changed", G_CALLBACK (action_state_changed_cb), &n_actions_state_changed);
actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
g_assert_cmpint (g_strv_length (actions), ==, 0);
g_strfreev (actions);
/* Actions are queried from the bus asynchronously after the first
* list_actions() call. Wait for the expected signals then check again. */
while (n_actions_added < G_N_ELEMENTS (exported_entries))
g_main_context_iteration (NULL, TRUE);
actions = g_action_group_list_actions (G_ACTION_GROUP (proxy));
g_assert_cmpint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
g_strfreev (actions);
/* check that calling "List" works too */
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"List",
NULL,
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_no_error (error);
g_assert_nonnull (v);
g_variant_get (v, "(^a&s)", &actions);
g_assert_cmpuint (g_strv_length (actions), ==, G_N_ELEMENTS (exported_entries));
g_free (actions);
g_variant_unref (v);
g_clear_object (&async_result);
/* check that calling "Describe" works */
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"Describe",
g_variant_new ("(s)", "copy"),
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_no_error (error);
g_assert_nonnull (v);
/* FIXME: there's an extra level of tuplelization in here */
g_variant_get (v, "((bgav))", &enabled, &param, &iter);
g_assert_true (enabled);
g_assert_cmpstr (param, ==, "");
g_assert_cmpint (g_variant_iter_n_children (iter), ==, 0);
g_free (param);
g_variant_iter_free (iter);
g_variant_unref (v);
g_clear_object (&async_result);
/* check that activating a parameterless action over D-Bus works */
g_assert_cmpint (activation_count ("undo"), ==, 0);
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"Activate",
g_variant_new ("(sava{sv})", "undo", NULL, NULL),
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_no_error (error);
g_assert_nonnull (v);
g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
g_variant_unref (v);
g_clear_object (&async_result);
g_assert_cmpint (activation_count ("undo"), ==, 1);
/* check that activating a parameterful action over D-Bus works */
g_assert_cmpint (activation_count ("lang"), ==, 0);
ensure_state (G_ACTION_GROUP (group), "lang", "'latin'");
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"Activate",
g_variant_new ("(s@ava{sv})", "lang", g_variant_new_parsed ("[<'spanish'>]"), NULL),
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_no_error (error);
g_assert_nonnull (v);
g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
g_variant_unref (v);
g_clear_object (&async_result);
g_assert_cmpint (activation_count ("lang"), ==, 1);
ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
/* check that various error conditions are rejected */
struct
{
const gchar *action_name;
GVariant *parameter; /* (owned floating) (nullable) */
}
activate_error_conditions[] =
{
{ "nope", NULL }, /* non-existent action */
{ "lang", g_variant_new_parsed ("[<@u 4>]") }, /* wrong parameter type */
{ "lang", NULL }, /* parameter missing */
{ "undo", g_variant_new_parsed ("[<'silly'>]") }, /* extraneous parameter */
};
for (gsize i = 0; i < G_N_ELEMENTS (activate_error_conditions); i++)
{
GVariant *parameter = g_steal_pointer (&activate_error_conditions[i].parameter);
const gchar *type_string = (parameter != NULL) ? "(s@ava{sv})" : "(sava{sv})";
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"Activate",
g_variant_new (type_string,
activate_error_conditions[i].action_name,
g_steal_pointer (&parameter),
NULL),
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_null (v);
g_clear_error (&error);
g_clear_object (&async_result);
}
/* check that setting an actions state over D-Bus works */
g_assert_cmpint (activation_count ("lang"), ==, 1);
ensure_state (G_ACTION_GROUP (group), "lang", "'spanish'");
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"SetState",
g_variant_new ("(sva{sv})", "lang", g_variant_new_string ("portuguese"), NULL),
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_no_error (error);
g_assert_nonnull (v);
g_assert_true (g_variant_is_of_type (v, G_VARIANT_TYPE_UNIT));
g_variant_unref (v);
g_clear_object (&async_result);
g_assert_cmpint (activation_count ("lang"), ==, 1);
ensure_state (G_ACTION_GROUP (group), "lang", "'portuguese'");
/* check that various error conditions are rejected */
struct
{
const gchar *action_name;
GVariant *state; /* (owned floating) (not nullable) */
}
set_state_error_conditions[] =
{
{ "nope", g_variant_new_string ("hello") }, /* non-existent action */
{ "undo", g_variant_new_string ("not stateful") }, /* not a stateful action */
{ "lang", g_variant_new_uint32 (3) }, /* wrong state type */
};
for (gsize i = 0; i < G_N_ELEMENTS (set_state_error_conditions); i++)
{
g_dbus_connection_call (bus,
g_dbus_connection_get_unique_name (bus),
"/",
"org.gtk.Actions",
"SetState",
g_variant_new ("(s@va{sv})",
set_state_error_conditions[i].action_name,
g_variant_new_variant (g_steal_pointer (&set_state_error_conditions[i].state)),
NULL),
NULL,
0,
G_MAXINT,
NULL,
async_result_cb,
&async_result);
while (async_result == NULL)
g_main_context_iteration (NULL, TRUE);
v = g_dbus_connection_call_finish (bus, async_result, &error);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_null (v);
g_clear_error (&error);
g_clear_object (&async_result);
}
/* test that the initial transfer works */
g_assert_true (G_IS_DBUS_ACTION_GROUP (proxy));
while (!compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)))
g_main_context_iteration (NULL, TRUE);
n_actions_state_changed = 0;
/* test that various changes get propagated from group to proxy */
n_actions_added = 0;
action = g_simple_action_new_stateful ("italic", NULL, g_variant_new_boolean (FALSE));
g_simple_action_group_insert (group, G_ACTION (action));
g_object_unref (action);
while (n_actions_added == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "cut"));
g_simple_action_set_enabled (action, FALSE);
while (n_actions_enabled_changed == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
action = G_SIMPLE_ACTION (g_simple_action_group_lookup (group, "bold"));
g_simple_action_set_state (action, g_variant_new_boolean (FALSE));
while (n_actions_state_changed == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
g_simple_action_group_remove (group, "italic");
while (n_actions_removed == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
/* test that activations and state changes propagate the other way */
n_actions_state_changed = 0;
g_assert_cmpint (activation_count ("copy"), ==, 0);
g_action_group_activate_action (G_ACTION_GROUP (proxy), "copy", NULL);
while (activation_count ("copy") == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (activation_count ("copy"), ==, 1);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
n_actions_state_changed = 0;
g_assert_cmpint (activation_count ("bold"), ==, 0);
g_action_group_activate_action (G_ACTION_GROUP (proxy), "bold", NULL);
while (n_actions_state_changed == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (activation_count ("bold"), ==, 1);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
v = g_action_group_get_action_state (G_ACTION_GROUP (group), "bold");
g_assert_true (g_variant_get_boolean (v));
g_variant_unref (v);
n_actions_state_changed = 0;
g_action_group_change_action_state (G_ACTION_GROUP (proxy), "bold", g_variant_new_boolean (FALSE));
while (n_actions_state_changed == 0)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (activation_count ("bold"), ==, 1);
g_assert_true (compare_action_groups (G_ACTION_GROUP (group), G_ACTION_GROUP (proxy)));
v = g_action_group_get_action_state (G_ACTION_GROUP (group), "bold");
g_assert_false (g_variant_get_boolean (v));
g_variant_unref (v);
g_dbus_connection_unexport_action_group (bus, id);
g_signal_handler_disconnect (proxy, added_signal_id);
g_signal_handler_disconnect (proxy, enabled_changed_signal_id);
g_signal_handler_disconnect (proxy, removed_signal_id);
g_signal_handler_disconnect (proxy, state_changed_signal_id);
g_object_unref (proxy);
g_object_unref (group);
g_object_unref (bus);
session_bus_down ();
}
static gpointer
do_export (gpointer data)
{
GActionGroup *group = data;
GMainContext *ctx;
gint i;
GError *error = NULL;
guint id;
GDBusConnection *bus;
GAction *action;
gchar *path;
ctx = g_main_context_new ();
g_main_context_push_thread_default (ctx);
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
path = g_strdup_printf("/%p", data);
for (i = 0; i < 10000; i++)
{
id = g_dbus_connection_export_action_group (bus, path, G_ACTION_GROUP (group), &error);
g_assert_no_error (error);
action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), "a");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
!g_action_get_enabled (action));
g_dbus_connection_unexport_action_group (bus, id);
while (g_main_context_iteration (ctx, FALSE));
}
g_free (path);
g_object_unref (bus);
g_main_context_pop_thread_default (ctx);
g_main_context_unref (ctx);
return NULL;
}
static void
test_dbus_threaded (void)
{
GSimpleActionGroup *group[10];
GThread *export[10];
static GActionEntry entries[] = {
{ "a", activate_action, NULL, NULL, NULL, { 0 } },
{ "b", activate_action, NULL, NULL, NULL, { 0 } },
};
gint i;
session_bus_up ();
for (i = 0; i < 10; i++)
{
group[i] = g_simple_action_group_new ();
g_simple_action_group_add_entries (group[i], entries, G_N_ELEMENTS (entries), NULL);
export[i] = g_thread_new ("export", do_export, group[i]);
}
for (i = 0; i < 10; i++)
g_thread_join (export[i]);
for (i = 0; i < 10; i++)
g_object_unref (group[i]);
session_bus_down ();
}
G_GNUC_END_IGNORE_DEPRECATIONS
static void
test_bug679509 (void)
{
GDBusConnection *bus;
GDBusActionGroup *proxy;
gboolean timed_out = FALSE;
session_bus_up ();
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
proxy = g_dbus_action_group_get (bus, g_dbus_connection_get_unique_name (bus), "/");
g_strfreev (g_action_group_list_actions (G_ACTION_GROUP (proxy)));
g_object_unref (proxy);
g_timeout_add (100, timeout_cb, &timed_out);
while (!timed_out)
g_main_context_iteration (NULL, TRUE);
g_object_unref (bus);
session_bus_down ();
}
static gchar *state_change_log;
static void
state_changed (GActionGroup *group,
const gchar *action_name,
GVariant *value,
gpointer user_data)
{
GString *string;
g_assert_false (state_change_log);
string = g_string_new (action_name);
g_string_append_c (string, ':');
g_variant_print_string (value, string, TRUE);
state_change_log = g_string_free (string, FALSE);
}
static void
verify_changed (const gchar *log_entry)
{
g_assert_cmpstr (state_change_log, ==, log_entry);
g_clear_pointer (&state_change_log, g_free);
}
static void
test_property_actions (void)
{
GSimpleActionGroup *group;
GPropertyAction *action;
GSocketClient *client;
GApplication *app;
gchar *name;
GVariantType *ptype, *stype;
gboolean enabled;
GVariant *state;
group = g_simple_action_group_new ();
g_signal_connect (group, "action-state-changed", G_CALLBACK (state_changed), NULL);
client = g_socket_client_new ();
app = g_application_new ("org.gtk.test", 0);
/* string... */
action = g_property_action_new ("app-id", app, "application-id");
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_object_unref (action);
/* uint... */
action = g_property_action_new ("keepalive", app, "inactivity-timeout");
g_object_get (action, "name", &name, "parameter-type", &ptype, "enabled", &enabled, "state-type", &stype, "state", &state, NULL);
g_assert_cmpstr (name, ==, "keepalive");
g_assert_true (enabled);
g_free (name);
g_variant_type_free (ptype);
g_variant_type_free (stype);
g_variant_unref (state);
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_object_unref (action);
/* bool... */
action = g_property_action_new ("tls", client, "tls");
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_object_unref (action);
/* inverted */
action = g_object_new (G_TYPE_PROPERTY_ACTION,
"name", "disable-proxy",
"object", client,
"property-name", "enable-proxy",
"invert-boolean", TRUE,
NULL);
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_object_unref (action);
/* enum... */
action = g_property_action_new ("type", client, "type");
g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action));
g_object_unref (action);
/* the objects should be held alive by the actions... */
g_object_unref (client);
g_object_unref (app);
ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
ensure_state (G_ACTION_GROUP (group), "tls", "false");
ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
verify_changed (NULL);
/* some string tests... */
g_action_group_change_action_state (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test2"));
verify_changed ("app-id:'org.gtk.test2'");
g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test2");
ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test2'");
g_action_group_activate_action (G_ACTION_GROUP (group), "app-id", g_variant_new ("s", "org.gtk.test3"));
verify_changed ("app-id:'org.gtk.test3'");
g_assert_cmpstr (g_application_get_application_id (app), ==, "org.gtk.test3");
ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test3'");
g_application_set_application_id (app, "org.gtk.test");
verify_changed ("app-id:'org.gtk.test'");
ensure_state (G_ACTION_GROUP (group), "app-id", "'org.gtk.test'");
/* uint tests */
g_action_group_change_action_state (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 1234));
verify_changed ("keepalive:uint32 1234");
g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 1234);
ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 1234");
g_action_group_activate_action (G_ACTION_GROUP (group), "keepalive", g_variant_new ("u", 5678));
verify_changed ("keepalive:uint32 5678");
g_assert_cmpuint (g_application_get_inactivity_timeout (app), ==, 5678);
ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 5678");
g_application_set_inactivity_timeout (app, 0);
verify_changed ("keepalive:uint32 0");
ensure_state (G_ACTION_GROUP (group), "keepalive", "uint32 0");
/* bool tests */
g_action_group_change_action_state (G_ACTION_GROUP (group), "tls", g_variant_new ("b", TRUE));
verify_changed ("tls:true");
g_assert_true (g_socket_client_get_tls (client));
ensure_state (G_ACTION_GROUP (group), "tls", "true");
g_action_group_change_action_state (G_ACTION_GROUP (group), "disable-proxy", g_variant_new ("b", TRUE));
verify_changed ("disable-proxy:true");
ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
g_assert_false (g_socket_client_get_enable_proxy (client));
/* test toggle true->false */
g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
verify_changed ("tls:false");
g_assert_false (g_socket_client_get_tls (client));
ensure_state (G_ACTION_GROUP (group), "tls", "false");
/* and now back false->true */
g_action_group_activate_action (G_ACTION_GROUP (group), "tls", NULL);
verify_changed ("tls:true");
g_assert_true (g_socket_client_get_tls (client));
ensure_state (G_ACTION_GROUP (group), "tls", "true");
g_socket_client_set_tls (client, FALSE);
verify_changed ("tls:false");
ensure_state (G_ACTION_GROUP (group), "tls", "false");
/* now do the same for the inverted action */
g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
verify_changed ("disable-proxy:false");
g_assert_true (g_socket_client_get_enable_proxy (client));
ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
g_action_group_activate_action (G_ACTION_GROUP (group), "disable-proxy", NULL);
verify_changed ("disable-proxy:true");
g_assert_false (g_socket_client_get_enable_proxy (client));
ensure_state (G_ACTION_GROUP (group), "disable-proxy", "true");
g_socket_client_set_enable_proxy (client, TRUE);
verify_changed ("disable-proxy:false");
ensure_state (G_ACTION_GROUP (group), "disable-proxy", "false");
/* enum tests */
g_action_group_change_action_state (G_ACTION_GROUP (group), "type", g_variant_new ("s", "datagram"));
verify_changed ("type:'datagram'");
g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_DATAGRAM);
ensure_state (G_ACTION_GROUP (group), "type", "'datagram'");
g_action_group_activate_action (G_ACTION_GROUP (group), "type", g_variant_new ("s", "stream"));
verify_changed ("type:'stream'");
g_assert_cmpint (g_socket_client_get_socket_type (client), ==, G_SOCKET_TYPE_STREAM);
ensure_state (G_ACTION_GROUP (group), "type", "'stream'");
g_socket_client_set_socket_type (client, G_SOCKET_TYPE_SEQPACKET);
verify_changed ("type:'seqpacket'");
ensure_state (G_ACTION_GROUP (group), "type", "'seqpacket'");
/* Check some error cases... */
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*non-existent*");
action = g_property_action_new ("foo", app, "xyz");
g_test_assert_expected_messages ();
g_object_unref (action);
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*writable*");
action = g_property_action_new ("foo", app, "is-registered");
g_test_assert_expected_messages ();
g_object_unref (action);
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_CRITICAL, "*type 'GSocketAddress'*");
action = g_property_action_new ("foo", client, "local-address");
g_test_assert_expected_messages ();
g_object_unref (action);
g_object_unref (group);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/actions/basic", test_basic);
g_test_add_func ("/actions/name", test_name);
g_test_add_func ("/actions/simplegroup", test_simple_group);
g_test_add_func ("/actions/stateful", test_stateful);
g_test_add_func ("/actions/default-activate", test_default_activate);
g_test_add_func ("/actions/entries", test_entries);
g_test_add_func ("/actions/parse-detailed", test_parse_detailed);
g_test_add_func ("/actions/dbus/export", test_dbus_export);
g_test_add_func ("/actions/dbus/threaded", test_dbus_threaded);
g_test_add_func ("/actions/dbus/bug679509", test_bug679509);
g_test_add_func ("/actions/property", test_property_actions);
return g_test_run ();
}