From 944e30d940d5832df506a272828f356f8534b723 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 30 Jul 2014 21:25:04 +0200 Subject: [PATCH] Add a11y state tests This adds a new test which can be scripted to trigger various event and action sequences, and record state changes in the accessibility layer. So far, there are a few tests verifying state changes when focus changes. Related to https://bugzilla.gnome.org/show_bug.cgi?id=715176 --- configure.ac | 1 + testsuite/a11y/Makefile.am | 7 +- testsuite/a11y/focus.c | 126 --------- testsuite/a11y/state/Makefile.am | 56 ++++ testsuite/a11y/state/focus1.in | 8 + testsuite/a11y/state/focus1.out | 4 + testsuite/a11y/state/focus1.ui | 22 ++ testsuite/a11y/state/focus2.in | 9 + testsuite/a11y/state/focus2.out | 7 + testsuite/a11y/state/focus2.ui | 20 ++ testsuite/a11y/state/state-record.c | 382 ++++++++++++++++++++++++++++ 11 files changed, 511 insertions(+), 131 deletions(-) delete mode 100644 testsuite/a11y/focus.c create mode 100644 testsuite/a11y/state/Makefile.am create mode 100644 testsuite/a11y/state/focus1.in create mode 100644 testsuite/a11y/state/focus1.out create mode 100644 testsuite/a11y/state/focus1.ui create mode 100644 testsuite/a11y/state/focus2.in create mode 100644 testsuite/a11y/state/focus2.out create mode 100644 testsuite/a11y/state/focus2.ui create mode 100644 testsuite/a11y/state/state-record.c diff --git a/configure.ac b/configure.ac index cd3f6fc78f..891094db72 100644 --- a/configure.ac +++ b/configure.ac @@ -1858,6 +1858,7 @@ tests/Makefile tests/visuals/Makefile testsuite/Makefile testsuite/a11y/Makefile +testsuite/a11y/state/Makefile testsuite/css/Makefile testsuite/css/parser/Makefile testsuite/gdk/Makefile diff --git a/testsuite/a11y/Makefile.am b/testsuite/a11y/Makefile.am index 5762efa44a..e8ab6068a3 100644 --- a/testsuite/a11y/Makefile.am +++ b/testsuite/a11y/Makefile.am @@ -1,6 +1,8 @@ include $(top_srcdir)/Makefile.decl NULL = +SUBDIRS = state + check_PROGRAMS = $(TEST_PROGS) AM_CPPFLAGS = \ @@ -39,11 +41,6 @@ TEST_PROGS += value TEST_PROGS += misc -# the focus test has no chance of working until -# all the idle handlers in gail are gone -# -# TEST_PROGS += focus - TEST_PROGS += tree-relationships TEST_PROGS += util diff --git a/testsuite/a11y/focus.c b/testsuite/a11y/focus.c deleted file mode 100644 index 1b83ea60a2..0000000000 --- a/testsuite/a11y/focus.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2011 Red Hat Inc. - * - * Author: - * Matthias Clasen - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library. If not, see . - */ - -#include -#include -const gchar data[] = - "" - " " - " True" - " " - " " - " True" - " " - " " - " True" - " entry1" - " " - " " - " " - " " - " True" - " entry2" - " " - " " - " " - " " - " " - ""; - -static void -got_active (GObject *win, GParamSpec *pspec, gpointer data) -{ - gtk_main_quit (); -} - -static void -test_focus_change (void) -{ - GtkBuilder *builder; - GError *error; - GtkWidget *window; - GtkWidget *entry1; - GtkWidget *entry2; - AtkObject *wa; - AtkObject *ea1; - AtkObject *ea2; - GtkWidget *focus; - AtkStateSet *set; - gboolean ret; - - builder = gtk_builder_new (); - error = NULL; - gtk_builder_add_from_string (builder, data, -1, &error); - g_assert_no_error (error); - window = (GtkWidget*)gtk_builder_get_object (builder, "window1"); - entry1 = (GtkWidget*)gtk_builder_get_object (builder, "entry1"); - entry2 = (GtkWidget*)gtk_builder_get_object (builder, "entry2"); - - wa = gtk_widget_get_accessible (window); - ea1 = gtk_widget_get_accessible (entry1); - ea2 = gtk_widget_get_accessible (entry2); - -#if 0 - g_signal_connect (window, "notify::is-active", G_CALLBACK (got_active), NULL); - gtk_widget_show (window); - gtk_main (); - g_assert (gtk_window_is_active (GTK_WINDOW (window))); -#endif - - focus = gtk_window_get_focus (GTK_WINDOW (window)); - g_assert (focus == entry1); - - set = atk_object_ref_state_set (ea1); - ret = atk_state_set_contains_state (set, ATK_STATE_FOCUSED); - g_assert (ret); - g_object_unref (set); - set = atk_object_ref_state_set (ea2); - ret = atk_state_set_contains_state (set, ATK_STATE_FOCUSED); - g_assert (!ret); - g_object_unref (set); - - gtk_widget_grab_focus (entry2); - - focus = gtk_window_get_focus (GTK_WINDOW (window)); - g_assert (focus == entry2); - - set = atk_object_ref_state_set (ea1); - ret = atk_state_set_contains_state (set, ATK_STATE_FOCUSED); - g_assert (!ret); - g_object_unref (set); - set = atk_object_ref_state_set (ea2); - ret = atk_state_set_contains_state (set, ATK_STATE_FOCUSED); - g_assert (ret); - g_object_unref (set); - - g_object_unref (builder); -} - - -int -main (int argc, char *argv[]) -{ - gtk_test_init (&argc, &argv, NULL); - - g_test_add_func ("/focus/change", test_focus_change); - - return g_test_run (); -} - diff --git a/testsuite/a11y/state/Makefile.am b/testsuite/a11y/state/Makefile.am new file mode 100644 index 0000000000..f457616f11 --- /dev/null +++ b/testsuite/a11y/state/Makefile.am @@ -0,0 +1,56 @@ +include $(top_srcdir)/Makefile.decl + +check_PROGRAMS = $(TEST_PROGS) + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir)/gdk \ + -I$(top_srcdir)/gdk \ + -DGDK_DISABLE_DEPRECATED \ + -DGTK_DISABLE_DEPRECATED \ + -DGTK_VERSION=\"$(GTK_VERSION)\"\ + $(GTK_DEBUG_FLAGS) \ + $(GTK_DEP_CFLAGS) + +LDADD = \ + $(top_builddir)/gdk/libgdk-3.la \ + $(top_builddir)/gtk/libgtk-3.la \ + $(GTK_DEP_LIBS) + +TESTS_ENVIRONMENT = \ + GIO_USE_VOLUME_MONITOR=unix \ + GSETTINGS_BACKEND=memory \ + G_ENABLE_DIAGNOSTIC=0 + +TEST_PROGS += state-record + +testdata = \ + focus1.ui focus1.in focus1.out \ + focus2.ui focus2.in focus2.out + +EXTRA_DIST += $(testdata) + +if BUILDOPT_INSTALL_TESTS +insttestdir = $(libexecdir)/installed-tests/$(PACKAGE) +insttest_PROGRAMS = $(TEST_PROGS) + +statetestdir = $(insttestdir)/state +statetest_DATA = $(testdata) + +a11ystate.test: Makefile + $(AM_V_GEN) (echo '[Test]' > $@.tmp; \ + echo 'Type=session' >> $@.tmp; \ + echo 'Exec=env G_ENABLE_DIAGNOSTIC=0 $(insttestdir)/state-record --directory $(statetestdir)' >> $@.tmp; \ + mv $@.tmp $@) + +testfiles = a11ystate.test + +DISTCLEANFILES = \ + $(testdata) \ + $(testfiles) + +testmetadir = $(datadir)/installed-tests/$(PACKAGE) +testmeta_DATA = $(testfiles) +endif + +-include $(top_srcdir)/git.mk diff --git a/testsuite/a11y/state/focus1.in b/testsuite/a11y/state/focus1.in new file mode 100644 index 0000000000..cfe3a315d0 --- /dev/null +++ b/testsuite/a11y/state/focus1.in @@ -0,0 +1,8 @@ +record entry1 entry2 +states focused +show window1 +focus entry1 +wait +focus entry2 +wait +destroy window1 diff --git a/testsuite/a11y/state/focus1.out b/testsuite/a11y/state/focus1.out new file mode 100644 index 0000000000..89c56433eb --- /dev/null +++ b/testsuite/a11y/state/focus1.out @@ -0,0 +1,4 @@ +entry1 focused 1 +entry1 focused 0 +entry2 focused 1 +entry2 focused 0 diff --git a/testsuite/a11y/state/focus1.ui b/testsuite/a11y/state/focus1.ui new file mode 100644 index 0000000000..48179e93c1 --- /dev/null +++ b/testsuite/a11y/state/focus1.ui @@ -0,0 +1,22 @@ + + + True + + + True + + + True + entry1 + + + + + True + entry2 + + + + + + diff --git a/testsuite/a11y/state/focus2.in b/testsuite/a11y/state/focus2.in new file mode 100644 index 0000000000..d66cf175f9 --- /dev/null +++ b/testsuite/a11y/state/focus2.in @@ -0,0 +1,9 @@ +record entry1 entry2 window1 window2 +states focused active +show window1 +show window2 +focus entry1 +wait +focus entry2 +wait +destroy window1 window2 diff --git a/testsuite/a11y/state/focus2.out b/testsuite/a11y/state/focus2.out new file mode 100644 index 0000000000..29202463c4 --- /dev/null +++ b/testsuite/a11y/state/focus2.out @@ -0,0 +1,7 @@ +entry1 focused 1 +window1 active 1 +entry1 focused 0 +window1 active 0 +entry2 focused 1 +window2 active 1 +entry2 focused 0 diff --git a/testsuite/a11y/state/focus2.ui b/testsuite/a11y/state/focus2.ui new file mode 100644 index 0000000000..2dda5ecc63 --- /dev/null +++ b/testsuite/a11y/state/focus2.ui @@ -0,0 +1,20 @@ + + + True + + + True + entry1 + + + + + True + + + True + entry2 + + + + diff --git a/testsuite/a11y/state/state-record.c b/testsuite/a11y/state/state-record.c new file mode 100644 index 0000000000..cf9313b1b0 --- /dev/null +++ b/testsuite/a11y/state/state-record.c @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2011 Red Hat Inc. + * + * Author: + * Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see . + */ + +#include "config.h" +#include +#include +#include + +static gchar **states; + +static void +record_state_change (AtkObject *accessible, + const gchar *state, + gboolean set, + GString *string) +{ + GtkWidget *w; + const gchar *name; + + if (states) + { + gint i; + + for (i = 0; states[i]; i++) + { + if (strcmp (states[i], state) == 0) + break; + } + if (states[i] == NULL) + return; + } + + w = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); + name = gtk_buildable_get_name (GTK_BUILDABLE (w)); + g_string_append_printf (string, "%s %s %d\n", name, state, set); +} + +static gboolean +stop (gpointer data) +{ + GMainLoop *loop = data; + + g_main_loop_quit (loop); + + return G_SOURCE_CONTINUE; +} + +static void +do_action (GtkBuilder *builder, const gchar *action, GString *string) +{ + gchar **parts; + gint len; + gint i; + + parts = g_strsplit (action, " ", 0); + len = g_strv_length (parts); + if (len > 0) + { + if (strcmp (parts[0], "record") == 0) + { + for (i = 1; i < len; i++) + { + GObject *o, *a; + + o = gtk_builder_get_object (builder, parts[i]); + if (ATK_IS_OBJECT (o)) + a = o; + else if (GTK_IS_WIDGET (o)) + a = G_OBJECT (gtk_widget_get_accessible (GTK_WIDGET (o))); + else + g_assert_not_reached (); + g_signal_connect (a, "state-change", G_CALLBACK (record_state_change), string); + } + } + else if (strcmp (parts[0], "states") == 0) + { + g_strfreev (states); + states = g_strdupv (parts); + } + else if (strcmp (parts[0], "destroy") == 0) + { + for (i = 1; i < len; i++) + { + GObject *o; + + o = gtk_builder_get_object (builder, parts[i]); + gtk_widget_destroy (GTK_WIDGET (o)); + } + } + else if (strcmp (parts[0], "show") == 0) + { + GObject *o; + + o = gtk_builder_get_object (builder, parts[1]); + gtk_widget_show_now (GTK_WIDGET (o)); + } + else if (strcmp (parts[0], "focus") == 0) + { + GObject *o; + + o = gtk_builder_get_object (builder, parts[1]); + gtk_widget_grab_focus (GTK_WIDGET (o)); + } + else if (strcmp (parts[0], "wait") == 0) + { + GMainLoop *loop; + gulong id; + + loop = g_main_loop_new (NULL, FALSE); + id = g_timeout_add (1000, stop, loop); + g_main_loop_run (loop); + g_source_remove (id); + g_main_loop_unref (loop); + } + } + g_free (parts); +} + +static void +record_events (const gchar *ui_file, + const gchar *in_file, + GString *string) +{ + GtkBuilder *builder; + GError *error; + gchar *contents; + gchar **actions; + gint i; + + builder = gtk_builder_new (); + error = NULL; + gtk_builder_add_from_file (builder, ui_file, &error); + g_assert_no_error (error); + + g_file_get_contents (in_file, &contents, NULL, &error); + g_assert_no_error (error); + actions = g_strsplit (contents, "\n", 0); + + for (i = 0; i < g_strv_length (actions); i++) + do_action (builder, actions[i], string); + + g_object_unref (builder); + + g_free (contents); + g_strfreev (actions); +} + +static gchar * +get_test_file (const gchar *test_file, + const gchar *extension, + gboolean must_exist) +{ + GString *file = g_string_new (NULL); + + if (g_str_has_suffix (test_file, ".ui")) + g_string_append_len (file, test_file, strlen (test_file) - strlen (".ui")); + else + g_string_append (file, test_file); + + g_string_append (file, extension); + + if (must_exist && + !g_file_test (file->str, G_FILE_TEST_EXISTS)) + { + g_string_free (file, TRUE); + return NULL; + } + + return g_string_free (file, FALSE); +} + +static char * +diff_with_file (const char *file1, + char *text, + gssize len, + GError **error) +{ + const char *command[] = { "diff", "-u", file1, NULL, NULL }; + char *diff, *tmpfile; + int fd; + + diff = NULL; + + if (len < 0) + len = strlen (text); + + /* write the text buffer to a temporary file */ + fd = g_file_open_tmp (NULL, &tmpfile, error); + if (fd < 0) + return NULL; + + if (write (fd, text, len) != (int) len) + { + close (fd); + g_set_error (error, + G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Could not write data to temporary file '%s'", tmpfile); + goto done; + } + close (fd); + command[3] = tmpfile; + + /* run diff command */ + g_spawn_sync (NULL, + (char **) command, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, + &diff, + NULL, NULL, + error); +done: + g_unlink (tmpfile); + g_free (tmpfile); + + return diff; +} + +static void +test_ui_file (GFile *file) +{ + gchar *ui_file, *in_file, *out_file; + GString *record; + GError *error = NULL; + + ui_file = g_file_get_path (file); + in_file = get_test_file (ui_file, ".in", TRUE); + out_file = get_test_file (ui_file, ".out", TRUE); + record = g_string_new (""); + + record_events (ui_file, in_file, record); + + if (out_file) + { + char *diff = diff_with_file (out_file, record->str, record->len, &error); + g_assert_no_error (error); + + if (diff && diff[0]) + { + g_printerr ("Contents don't match expected contents:\n%s", diff); + g_test_fail (); + g_free (diff); + } + } + else if (record->str[0]) + { + g_test_message ("Expected a reference file:\n%s", record->str); + g_test_fail (); + } + + g_string_free (record, TRUE); + g_free (in_file); + g_free (out_file); + g_free (ui_file); +} + +static void +add_test_for_file (GFile *file) +{ + g_test_add_vtable (g_file_get_path (file), + 0, + g_object_ref (file), + (GTestFixtureFunc) NULL, + (GTestFixtureFunc) test_ui_file, + (GTestFixtureFunc) g_object_unref); +} + +static int +compare_files (gconstpointer a, gconstpointer b) +{ + GFile *file1 = G_FILE (a); + GFile *file2 = G_FILE (b); + char *path1, *path2; + int result; + + path1 = g_file_get_path (file1); + path2 = g_file_get_path (file2); + + result = strcmp (path1, path2); + + g_free (path1); + g_free (path2); + + return result; +} + +static void +add_tests_for_files_in_directory (GFile *dir) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + GList *files; + GError *error = NULL; + + enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error); + g_assert_no_error (error); + files = NULL; + + while ((info = g_file_enumerator_next_file (enumerator, NULL, &error))) + { + const char *filename; + + filename = g_file_info_get_name (info); + + if (!g_str_has_suffix (filename, ".ui")) + { + g_object_unref (info); + continue; + } + + files = g_list_prepend (files, g_file_get_child (dir, filename)); + + g_object_unref (info); + } + + g_assert_no_error (error); + g_object_unref (enumerator); + + files = g_list_sort (files, compare_files); + g_list_foreach (files, (GFunc) add_test_for_file, NULL); + g_list_free_full (files, g_object_unref); +} + +static char *arg_base_dir = NULL; + +static const GOptionEntry test_args[] = { + { "directory", 'd', 0, G_OPTION_ARG_FILENAME, &arg_base_dir, + "Directory to run tests from", "DIR" }, + { NULL } +}; + +int +main (int argc, char *argv[]) +{ + const gchar *basedir; + GFile *dir; + GError *error = NULL; + GOptionContext *context; + + context = g_option_context_new ("- run GTK state tests"); + g_option_context_add_main_entries (context, test_args, NULL); + g_option_context_set_ignore_unknown_options (context, TRUE); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_print ("option parsing failed: %s\n", error->message); + return FALSE; + } + + g_option_context_free (context); + + gtk_test_init (&argc, &argv, NULL); + + if (arg_base_dir) + basedir = arg_base_dir; + else + basedir = g_test_get_dir (G_TEST_DIST); + + dir = g_file_new_for_path (basedir); + add_tests_for_files_in_directory (dir); + g_object_unref (dir); + + return g_test_run (); +} +