Added Gtk+ testing utilities.

* gtk/gtktestutils.h, gtk/gtktestutils.c: added unit test utility functions.
for the most part, the functions herein involve navigating and interacting
with dialog elements programatically, to automate user interaction tests of
dialogs and widgets.

* gtk/gtk.h: include gtk/gtktestutils.h as public API.

* gtk/gtk.symbols: added gtk_test_* symbols.

* gtk/Makefile.am: include gtktestutils.h and gtktestutils.c into the build.
generate gtktypefuncs.c which contains a list of all _get_type functions in
Gtk+ and Gdk.

svn path=/trunk/; revision=19010
This commit is contained in:
Tim Janik 2007-11-20 15:03:26 +00:00
parent 9e0b0194ed
commit 936d27a475
5 changed files with 885 additions and 1 deletions

View File

@ -279,6 +279,7 @@ gtk_public_h_sources = \
gtkstyle.h \
gtktable.h \
gtktearoffmenuitem.h \
gtktestutils.h \
gtktext.h \
gtktextbuffer.h \
gtktextbufferrichtext.h \
@ -559,6 +560,7 @@ gtk_base_c_sources = \
gtkstyle.c \
gtktable.c \
gtktearoffmenuitem.c \
gtktestutils.c \
gtktext.c \
gtktextbtree.c \
gtktextbuffer.c \
@ -714,6 +716,7 @@ gtk_built_private_headers = \
gtk_built_sources = \
gtkaliasdef.c \
gtktypebuiltins.c \
gtktypefuncs.c \
gtkmarshalers.c \
gtkmarshal.c \
${gtk_built_private_headers} \
@ -751,7 +754,7 @@ EXTRA_DIST += $(STOCK_ICONS)
# rules to generate built sources
#
# setup autogeneration dependencies
gen_sources = xgen-gdef xgen-gtbh xgen-gtic xgen-gmh xgen-gmc xgen-gmlh xgen-gmlc
gen_sources = xgen-gdef xgen-gtbh xgen-gtic xgen-gmh xgen-gmc xgen-gmlh xgen-gmlc xgen-gtfsrc.c xgen-gtf
CLEANFILES = $(gen_sources)
BUILT_SOURCES = $(gtk_built_sources)
@ -817,6 +820,14 @@ gtktypebuiltins.c: @REBUILD@ $(gtk_public_h_sources) Makefile
&& cp xgen-gtbc gtktypebuiltins.c \
&& rm -f xgen-gtbc
gtktypefuncs.c: @REBUILD@ $(top_srcdir)/gtk/*.h $(top_srcdir)/gdk/*.h Makefile
echo '#include <gtk/gtk.h>' > xgen-gtfsrc.c && \
${CPP} $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) xgen-gtfsrc.c | \
egrep -o '\<g[td]k_[a-zA-Z0-9_]+_get_type\>' | sort | uniq | \
sed '{ s/^/vgt=/; s/$$/();/; }' > xgen-gtf \
&& cp xgen-gtf $@ && rm -f xgen-gtf
gtktestutils.c: gtktypefuncs.c
# target platform:
lib_LTLIBRARIES = $(gtktargetlib)

View File

@ -183,6 +183,7 @@
#include <gtk/gtktoolitem.h>
#include <gtk/gtktooltip.h>
#include <gtk/gtktooltips.h>
#include <gtk/gtktestutils.h>
#include <gtk/gtktree.h>
#include <gtk/gtktreednd.h>
#include <gtk/gtktreeitem.h>

View File

@ -3253,6 +3253,29 @@ gtk_recent_info_match
#endif
#endif
#if IN_HEADER(__GTK_TEST_UTILS_H__)
#if IN_FILE(__GTK_TEST_UTILS_C__)
gtk_test_create_simple_window
gtk_test_create_widget
gtk_test_display_button_window
gtk_test_find_label
gtk_test_find_sibling
gtk_test_find_widget
gtk_test_init
gtk_test_register_all_types
gtk_test_simulate_button
gtk_test_simulate_key
gtk_test_slider_get_value
gtk_test_slider_set_perc
gtk_test_spin_button_click
gtk_test_text_get
gtk_test_text_set
gtk_test_widget_click
gtk_test_widget_send_key
gtk_test_xserver_render_sync
#endif
#endif
#if IN_HEADER(__GTK_TEXT_BUFFER_RICH_TEXT_H__)
#if IN_FILE(__GTK_TEXT_BUFFER_RICH_TEXT_C__)
gtk_text_buffer_deserialize

769
gtk/gtktestutils.c Normal file
View File

@ -0,0 +1,769 @@
/* Gtk+ testing utilities
* Copyright (C) 2007 Tim Janik
*
* 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 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gtktestutils.h"
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <x11/gdkx.h>
#include "gtkalias.h"
#include <X11/Xlib.h>
#include <locale.h>
#include <string.h>
#include <math.h>
/* --- test utilities --- */
/**
* gtk_test_init:
* @argc: Address of the <parameter>argc</parameter> parameter of the
* main() function. Changed if any arguments were handled.
* @argv: Address of the <parameter>argv</parameter> parameter of main().
* Any parameters understood by g_test_init() or gtk_init() are
* stripped before return.
*
* This function is used to initialize a Gtk+ test program.
* It will in turn call g_test_init() and gtk_init() to properly
* initialize the testing framework and graphical toolkit.
* It'll also set the program's locale to "C" and prevent loading of
* rc files and Gtk+ modules. This is done to make tets program
* environments as deterministic as possible.
* Like gtk_init() and g_test_init(), any known arguments will be
* processed and stripped from @argc and @argv.
**/
void
gtk_test_init (int *argcp,
char ***argvp,
...)
{
/* - enter C locale
* - call g_test_init();
* - call gtk_init();
* - prevent RC files from loading;
* - prevent Gtk modules from loading;
* - supply mock object for GtkSettings
* FUTURE TODO:
* - this function could install a mock object around GtkSettings
*/
// FIXME: g_test_init (argcp, argvp, NULL);
g_setenv ("GTK_MODULES", "", TRUE);
g_setenv ("GTK2_RC_FILES", "/dev/null", TRUE);
gtk_disable_setlocale();
setlocale (LC_ALL, "C");
gtk_init (argcp, argvp);
}
static GSList*
test_find_widget_input_windows (GtkWidget *widget,
gboolean input_only)
{
GList *node, *children;
GSList *matches = NULL;
gpointer udata;
gdk_window_get_user_data (widget->window, &udata);
if (udata == widget && (!input_only || (GDK_IS_WINDOW (widget->window) && GDK_WINDOW_OBJECT (widget->window)->input_only)))
matches = g_slist_prepend (matches, widget->window);
children = gdk_window_get_children (gtk_widget_get_parent_window (widget));
for (node = children; node; node = node->next)
{
gdk_window_get_user_data (node->data, &udata);
if (udata == widget && (!input_only || (GDK_IS_WINDOW (node->data) && GDK_WINDOW_OBJECT (node->data)->input_only)))
matches = g_slist_prepend (matches, node->data);
}
return g_slist_reverse (matches);
}
/**
* gtk_test_simulate_key
* @window: Gdk window to simulate a key event for.
* @x: x coordinate within @window for the key event.
* @y: y coordinate within @window for the key event.
* @keyval: A Gdk keyboard value.
* @modifiers: Keyboard modifiers the event is setup with.
* @press_or_release: %TRUE to generate key press events, %FALSE to generate key release events.
*
* This function is intended to be used in Gtk+ test programs.
* If (@x,@y) are > (-1,-1), it will warp the mouse pointer to
* the given (@x,@y) corrdinates within @window and simulate a
* key press or release event.
* When the mouse pointer is warped to the target location, use
* of this function outside of test programs that run in their
* own virtual windowing system (e.g. Xvfb) is not recommended.
* If (@x,@y) are passed as (-1,-1), the mouse pointer will not
* be warped and @window origin will be used as mouse pointer
* location for the event.
* Also, gtk_test_simulate_key() is a fairly low level function,
* for most testing purposes, gtk_test_widget_send_key() is the
* right function to call which will generate a key press event
* followed by its accompanying key release event.
*
* Returns: wether all actions neccessary for a key event simulation were carried out successfully.
**/
gboolean
gtk_test_simulate_key (GdkWindow *window,
gint x,
gint y,
guint keyval,
GdkModifierType modifiers,
gboolean press_or_release)
{
GdkScreen *screen = gdk_colormap_get_screen (gdk_drawable_get_colormap (window));
GdkKeymapKey *keys = NULL;
gboolean success;
gint n_keys = 0;
XKeyEvent xev = {
0, /* type */
0, /* serial */
1, /* send_event */
};
if (x < 0 && y < 0)
{
gdk_drawable_get_size (window, &x, &y);
x /= 2;
y /= 2;
}
xev.type = press_or_release ? KeyPress : KeyRelease;
xev.display = GDK_DRAWABLE_XDISPLAY (window);
xev.window = GDK_WINDOW_XID (window);
xev.root = RootWindow (xev.display, GDK_SCREEN_XNUMBER (screen));
xev.subwindow = 0;
xev.time = 0;
xev.x = MAX (x, 0);
xev.y = MAX (y, 0);
xev.x_root = 0;
xev.y_root = 0;
xev.state = modifiers;
xev.keycode = 0;
success = gdk_keymap_get_entries_for_keyval (gdk_keymap_get_for_display (gdk_drawable_get_display (window)), keyval, &keys, &n_keys);
success &= n_keys > 0;
if (success)
{
gint i;
for (i = 0; i < n_keys; i++)
if (keys[i].group == 0 && keys[i].level == 0)
{
xev.keycode = keys[i].keycode;
break;
}
if (i >= n_keys) /* no match for group==0 and level==0 */
xev.keycode = keys[0].keycode;
}
g_free (keys);
if (!success)
return FALSE;
gdk_error_trap_push ();
xev.same_screen = XTranslateCoordinates (xev.display, xev.window, xev.root,
xev.x, xev.y, &xev.x_root, &xev.y_root,
&xev.subwindow);
if (!xev.subwindow)
xev.subwindow = xev.window;
success &= xev.same_screen;
if (x >= 0 && y >= 0)
success &= 0 != XWarpPointer (xev.display, None, xev.window, 0, 0, 0, 0, xev.x, xev.y);
success &= 0 != XSendEvent (xev.display, xev.window, True, press_or_release ? KeyPressMask : KeyReleaseMask, (XEvent*) &xev);
XSync (xev.display, False);
success &= 0 == gdk_error_trap_pop();
return success;
}
/**
* gtk_test_widget_send_key
* @widget: Widget to generate a key press and release on.
* @keyval: A Gdk keyboard value.
* @modifiers: Keyboard modifiers the event is setup with.
*
* This function will generate keyboard press and release events in
* the middle of the first GdkWindow found that belongs to @widget.
* For %GTK_NO_WINDOW widgets like GtkButton, this will often be an
* input-only event window. For other widgets, this is usually widget->window.
* Certain caveats should be considered when using this function, in
* particular because the mouse pointer is warped to the key press
* location, see gtk_test_simulate_key() for details.
*
* Returns: wether all actions neccessary for the key event simulation were carried out successfully.
**/
gboolean
gtk_test_widget_send_key (GtkWidget *widget,
guint keyval,
GdkModifierType modifiers)
{
gboolean k1res, k2res;
GSList *iwindows = test_find_widget_input_windows (widget, FALSE);
if (!iwindows)
iwindows = test_find_widget_input_windows (widget, TRUE);
if (!iwindows)
return FALSE;
k1res = gtk_test_simulate_key (iwindows->data, -1, -1, keyval, modifiers, TRUE);
k2res = gtk_test_simulate_key (iwindows->data, -1, -1, keyval, modifiers, FALSE);
g_slist_free (iwindows);
return k1res && k2res;
}
/**
* gtk_test_simulate_button
* @window: Gdk window to simulate a button event for.
* @x: x coordinate within @window for the button event.
* @y: y coordinate within @window for the button event.
* @button: Number of the pointer button for the event, usually 1, 2 or 3.
* @modifiers: Keyboard modifiers the event is setup with.
* @press_or_release: %TRUE to generate button press events, %FALSE to generate button release events.
*
* This function is intended to be used in Gtk+ test programs.
* It will warp the mouse pointer to the given (@x,@y) corrdinates
* within @window and simulate a button press or release event.
* Because the mouse pointer needs to be warped to the target
* location, use of this function outside of test programs that
* run in their own virtual windowing system (e.g. Xvfb) is not
* recommended.
* Also, gtk_test_simulate_button() is a fairly low level function,
* for most testing purposes, gtk_test_widget_click() is the right
* function to call which will generate a button press event followed
* by its accompanying button release event.
*
* Returns: wether all actions neccessary for a button event simulation were carried out successfully.
**/
gboolean
gtk_test_simulate_button (GdkWindow *window,
gint x,
gint y,
guint button, /*1..3*/
GdkModifierType modifiers,
gboolean press_or_release)
{
GdkScreen *screen = gdk_colormap_get_screen (gdk_drawable_get_colormap (window));
XButtonEvent xev = {
0, /* type */
0, /* serial */
1, /* send_event */
};
if (x < 0 && y < 0)
{
gdk_drawable_get_size (window, &x, &y);
x /= 2;
y /= 2;
}
xev.type = press_or_release ? ButtonPress : ButtonRelease;
xev.display = GDK_DRAWABLE_XDISPLAY (window);
xev.window = GDK_WINDOW_XID (window);
xev.root = RootWindow (xev.display, GDK_SCREEN_XNUMBER (screen));
xev.subwindow = 0;
xev.time = 0;
xev.x = x;
xev.y = y;
xev.x_root = 0;
xev.y_root = 0;
xev.state = modifiers;
xev.button = button;
gdk_error_trap_push ();
xev.same_screen = XTranslateCoordinates (xev.display, xev.window, xev.root,
xev.x, xev.y, &xev.x_root, &xev.y_root,
&xev.subwindow);
if (!xev.subwindow)
xev.subwindow = xev.window;
gboolean success = xev.same_screen;
success &= 0 != XWarpPointer (xev.display, None, xev.window, 0, 0, 0, 0, xev.x, xev.y);
success &= 0 != XSendEvent (xev.display, xev.window, True, press_or_release ? ButtonPressMask : ButtonReleaseMask, (XEvent*) &xev);
XSync (xev.display, False);
success &= 0 == gdk_error_trap_pop();
return success;
}
/**
* gtk_test_widget_click
* @widget: Widget to generate a button click on.
* @button: Number of the pointer button for the event, usually 1, 2 or 3.
* @modifiers: Keyboard modifiers the event is setup with.
*
* This function will generate a @button click (button press and button
* release event) in the middle of the first GdkWindow found that belongs
* to @widget.
* For %GTK_NO_WINDOW widgets like GtkButton, this will often be an
* input-only event window. For other widgets, this is usually widget->window.
* Certain caveats should be considered when using this function, in
* particular because the mouse pointer is warped to the button click
* location, see gtk_test_simulate_button() for details.
*
* Returns: wether all actions neccessary for the button click simulation were carried out successfully.
**/
gboolean
gtk_test_widget_click (GtkWidget *widget,
guint button,
GdkModifierType modifiers)
{
gboolean b1res, b2res;
GSList *iwindows = test_find_widget_input_windows (widget, FALSE);
if (!iwindows)
iwindows = test_find_widget_input_windows (widget, TRUE);
if (!iwindows)
return FALSE;
b1res = gtk_test_simulate_button (iwindows->data, -1, -1, button, modifiers, TRUE);
b2res = gtk_test_simulate_button (iwindows->data, -1, -1, button, modifiers, FALSE);
g_slist_free (iwindows);
return b1res && b2res;
}
/**
* gtk_test_spin_button_click
* @spinner: valid GtkSpinButton widget.
* @button: Number of the pointer button for the event, usually 1, 2 or 3.
* @upwards: %TRUE for upwards arrow click, %FALSE for downwards arrow click.
*
* This function will generate a @button click in the upwards or downwards
* spin button arrow areas, usually leading to an increase or decrease of
* spin button's value.
*
* Returns: wether all actions neccessary for the button click simulation were carried out successfully.
**/
gboolean
gtk_test_spin_button_click (GtkSpinButton *spinner,
guint button,
gboolean upwards)
{
gboolean b1res = FALSE, b2res = FALSE;
if (spinner->panel)
{
gint width, height, pos;
gdk_drawable_get_size (spinner->panel, &width, &height);
pos = upwards ? 0 : height - 1;
b1res = gtk_test_simulate_button (spinner->panel, width - 1, pos, button, 0, TRUE);
b2res = gtk_test_simulate_button (spinner->panel, width - 1, pos, button, 0, FALSE);
}
return b1res && b2res;
}
/**
* gtk_test_find_label
* @widget: Valid label or container widget.
* @label_pattern: Shell-glob pattern to match a label string.
*
* This function will search @widget and all its descendants for a GtkLabel
* widget with a text string matching @label_pattern.
* The @label_pattern may contain asterisks '*' and question marks '?' as
* placeholders, g_pattern_match() is used for the matching.
* Note that locales other than "C" tend to alter (translate" label strings,
* so this function is genrally only useful in test programs with
* predetermined locales, see gtk_test_init() for more details.
*
* Returns: a GtkLabel widget if any is found.
**/
GtkWidget*
gtk_test_find_label (GtkWidget *widget,
const gchar *label_pattern)
{
if (GTK_IS_LABEL (widget))
{
const gchar *text = gtk_label_get_text (GTK_LABEL (widget));
if (g_pattern_match_simple (label_pattern, text))
return widget;
}
if (GTK_IS_CONTAINER (widget))
{
GList *node, *list = gtk_container_get_children (GTK_CONTAINER (widget));
for (node = list; node; node = node->next)
{
GtkWidget *label = gtk_test_find_label (node->data, label_pattern);
if (label)
return label;
}
g_list_free (list);
}
return NULL;
}
static GList*
test_list_descendants (GtkWidget *widget,
GType widget_type)
{
GList *results = NULL;
if (GTK_IS_CONTAINER (widget))
{
GList *node, *list = gtk_container_get_children (GTK_CONTAINER (widget));
for (node = list; node; node = node->next)
{
if (!widget_type || g_type_is_a (G_OBJECT_TYPE (node->data), widget_type))
results = g_list_prepend (results, node->data);
else
results = g_list_concat (results, test_list_descendants (node->data, widget_type));
}
g_list_free (list);
}
return results;
}
static int
widget_geo_dist (GtkWidget *a,
GtkWidget *b,
GtkWidget *base)
{
int ax0, ay0, ax1, ay1, bx0, by0, bx1, by1, xdist = 0, ydist = 0;
if (!gtk_widget_translate_coordinates (a, base, 0, 0, &ax0, &ay0) ||
!gtk_widget_translate_coordinates (a, base, a->allocation.width, a->allocation.height, &ax1, &ay1))
return -G_MAXINT;
if (!gtk_widget_translate_coordinates (b, base, 0, 0, &bx0, &by0) ||
!gtk_widget_translate_coordinates (b, base, b->allocation.width, b->allocation.height, &bx1, &by1))
return +G_MAXINT;
if (bx0 >= ax1)
xdist = bx0 - ax1;
else if (ax0 >= bx1)
xdist = ax0 - bx1;
if (by0 >= ay1)
ydist = by0 - ay1;
else if (ay0 >= by1)
ydist = ay0 - by1;
return xdist + ydist;
}
static int
widget_geo_cmp (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
gpointer *data = user_data;
GtkWidget *wa = (void*) a, *wb = (void*) b, *toplevel = data[0], *base_widget = data[1];
int adist = widget_geo_dist (wa, base_widget, toplevel);
int bdist = widget_geo_dist (wb, base_widget, toplevel);
return adist > bdist ? +1 : adist == bdist ? 0 : -1;
}
/**
* gtk_test_find_sibling
* @base_widget: Valid widget, part of a widget hierarchy
* @widget_type: Type of a aearched for sibling widget
*
* This function will search siblings of @base_widget and siblings of its
* ancestors for all widgets matching @widget_type.
* Of the matching widgets, the one that is geometrically closest to
* @base_widget will be returned.
* The general purpose of this function is to find the most likely "action"
* widget, relative to another labeling widget. Such as finding a
* button or text entry widget, given it's corresponding label widget.
*
* Returns: a widget of type @widget_type if any is found.
**/
GtkWidget*
gtk_test_find_sibling (GtkWidget *base_widget,
GType widget_type)
{
GList *siblings = NULL;
GtkWidget *tmpwidget = base_widget;
gpointer data[2];
/* find all sibling candidates */
while (tmpwidget)
{
tmpwidget = tmpwidget->parent;
siblings = g_list_concat (siblings, test_list_descendants (tmpwidget, widget_type));
}
/* sort them by distance to base_widget */
data[0] = gtk_widget_get_toplevel (base_widget);
data[1] = base_widget;
siblings = g_list_sort_with_data (siblings, widget_geo_cmp, data);
/* pick nearest != base_widget */
siblings = g_list_remove (siblings, base_widget);
tmpwidget = siblings ? siblings->data : NULL;
g_list_free (siblings);
return tmpwidget;
}
/**
* gtk_test_find_widget
* @widget: Container widget, usually a GtkWindow.
* @label_pattern: Shell-glob pattern to match a label string.
* @widget_type: Type of a aearched for label sibling widget.
*
* This function will search the descendants of @widget for a widget
* of type @widget_type that has a label matching @label_pattern next
* to it. This is most useful for automated GUI testing, e.g. to find
* the "OK" button in a dialog and synthesize clicks on it.
* However see gtk_test_find_label(), gtk_test_find_sibling() and
* gtk_test_widget_click() for possible caveats involving the search of
* such widgets and synthesizing widget events.
*
* Returns: a valid widget if any is found or %NULL.
**/
GtkWidget*
gtk_test_find_widget (GtkWidget *widget,
const gchar *label_pattern,
GType widget_type)
{
GtkWidget *label = gtk_test_find_label (widget, label_pattern);
if (!label)
label = gtk_test_find_label (gtk_widget_get_toplevel (widget), label_pattern);
if (label)
return gtk_test_find_sibling (label, widget_type);
return NULL;
}
/**
* gtk_test_slider_set_perc
* @widget: valid widget pointer.
* @percentage: value between 0 and 100.
*
* This function will adjust theslider position of all GtkRange
* based widgets, such as scrollbars or scales, it'll also adjust
* spin buttons. The adjustment value of tehse widgets is set to
* a value between the lower and upper limits, according to the
* @percentage argument.
**/
void
gtk_test_slider_set_perc (GtkWidget *widget,
double percentage)
{
GtkAdjustment *adjustment = NULL;
if (GTK_IS_RANGE (widget))
adjustment = gtk_range_get_adjustment (GTK_RANGE (widget));
else if (GTK_IS_SPIN_BUTTON (widget))
adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget));
if (adjustment)
gtk_adjustment_set_value (adjustment, adjustment->lower + (adjustment->upper - adjustment->lower - adjustment->page_size) * percentage * 0.01);
}
/**
* gtk_test_slider_get_value
* @widget: valid widget pointer.
*
* Retrive the literal adjustment value for GtkRange based
* widgets and spin buttons. Note that the value returned by
* this function is anything between the lower and upper bounds
* of the adjustment belonging to @widget, and is not a percentage
* as passed in to gtk_test_slider_set_perc().
*
* Returns: adjustment->value for an adjustment belonging to @widget.
**/
double
gtk_test_slider_get_value (GtkWidget *widget)
{
GtkAdjustment *adjustment = NULL;
if (GTK_IS_RANGE (widget))
adjustment = gtk_range_get_adjustment (GTK_RANGE (widget));
else if (GTK_IS_SPIN_BUTTON (widget))
adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget));
return adjustment ? adjustment->value : 0;
}
/**
* gtk_test_text_set
* @widget: valid widget pointer.
* @string: a 0-terminated C string
*
* Set the text string of @widget to @string if it is a GtkLabel,
* GtkEditable (entry and text widgets) or GtkTextView.
**/
void
gtk_test_text_set (GtkWidget *widget,
const gchar *string)
{
if (GTK_IS_LABEL (widget))
gtk_label_set_text (GTK_LABEL (widget), string);
else if (GTK_IS_EDITABLE (widget))
{
int pos = 0;
gtk_editable_delete_text (GTK_EDITABLE (widget), 0, -1);
gtk_editable_insert_text (GTK_EDITABLE (widget), string, -1, &pos);
}
else if (GTK_IS_TEXT_VIEW (widget))
{
GtkTextBuffer *tbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
gtk_text_buffer_set_text (tbuffer, string, -1);
}
}
/**
* gtk_test_text_get
* @widget: valid widget pointer.
*
* Retrive the text string of @widget if it is a GtkLabel,
* GtkEditable (entry and text widgets) or GtkTextView.
*
* Returns: new 0-terminated C string, needs to be releaed with g_free().
**/
gchar*
gtk_test_text_get (GtkWidget *widget)
{
if (GTK_IS_LABEL (widget))
return g_strdup (gtk_label_get_text (GTK_LABEL (widget)));
else if (GTK_IS_EDITABLE (widget))
{
return g_strdup (gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1));
}
else if (GTK_IS_TEXT_VIEW (widget))
{
GtkTextBuffer *tbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
GtkTextIter start, end;
gtk_text_buffer_get_start_iter (tbuffer, &start);
gtk_text_buffer_get_end_iter (tbuffer, &end);
return gtk_text_buffer_get_text (tbuffer, &start, &end, FALSE);
}
return NULL;
}
/**
* gtk_test_create_widget
* @widget_type: valid widget type.
* @first_property_name: Name of first property to set or %NULL
*
* This function wraps g_object_new() for widget types.
* It'll automatically show all created non window widgets, also
* g_object_ref_sink() them (to keep them alive across a running test)
* and set them up for destruction during test teardown phase.
*
* Returns: a newly created widget.
*/
GtkWidget*
gtk_test_create_widget (GType widget_type,
const gchar *first_property_name,
...)
{
GtkWidget *widget;
va_list var_args;
g_return_val_if_fail (g_type_is_a (widget_type, GTK_TYPE_WIDGET), NULL);
va_start (var_args, first_property_name);
widget = (GtkWidget*) g_object_new_valist (widget_type, first_property_name, var_args);
va_end (var_args);
if (widget)
{
if (!GTK_IS_WINDOW (widget))
gtk_widget_show (widget);
g_object_ref_sink (widget);
#ifdef __G_TESTFRAMEWORK_H__ /* FIXME */
g_test_queue_unref (widget);
g_test_queue_destroy ((GDestroyNotify) gtk_widget_destroy, widget);
#endif
}
return widget;
}
static void
try_main_quit (void)
{
if (gtk_main_level())
gtk_main_quit();
}
static int
test_increment_intp (int *intp)
{
*intp += 1;
return 1; /* TRUE in case we're connected to event signals */
}
/**
* gtk_test_display_button_window
* @window_title: Title of the window to be displayed.
* @dialog_text: Text inside the window to be displayed.
* @...: %NULL terminated list of (const char *label, int *nump) pairs.
*
* Create a window with window title @window_title, text contents @dialog_text,
* and a number of buttons, according to the paired argument list given
* as @... parameters.
* Each button is created with a @label and a ::clicked signal handler that
* incremrents the integer stored in @nump.
* The window will be automatically shown with gtk_widget_show_now() after
* creation, so when this function returns it has already been mapped,
* resized and positioned on screen.
* The window will quit any running gtk_main()-loop when destroyed, and it
* will automatically be destroyed upon test function teardown.
*
* Returns: a widget pointer to the newly created GtkWindow.
**/
GtkWidget*
gtk_test_display_button_window (const gchar *window_title,
const gchar *dialog_text,
...) /* NULL terminated list of (label, &int) pairs */
{
va_list var_args;
GtkWidget *window = gtk_test_create_widget (GTK_TYPE_WINDOW, "title", window_title, NULL);
GtkWidget *vbox = gtk_test_create_widget (GTK_TYPE_VBOX, "parent", window, NULL);
const char *arg1;
gtk_test_create_widget (GTK_TYPE_LABEL, "label", dialog_text, "parent", vbox, NULL);
g_signal_connect (window, "destroy", G_CALLBACK (try_main_quit), NULL);
va_start (var_args, dialog_text);
arg1 = va_arg (var_args, const char*);
while (arg1)
{
int *arg2 = va_arg (var_args, int*);
GtkWidget *button = gtk_test_create_widget (GTK_TYPE_BUTTON, "label", arg1, "parent", vbox, NULL);
g_signal_connect_swapped (button, "clicked", G_CALLBACK (test_increment_intp), arg2);
arg1 = va_arg (var_args, const char*);
}
va_end (var_args);
gtk_widget_show_all (vbox);
gtk_widget_show_now (window);
while (gtk_events_pending ())
gtk_main_iteration ();
return window;
}
/**
* gtk_test_create_simple_window
* @window_title: Title of the window to be displayed.
* @dialog_text: Text inside the window to be displayed.
*
* Create a simple window with window title @window_title and
* text contents @dialog_text.
* The window will quit any running gtk_main()-loop when destroyed, and it
* will automatically be destroyed upon test function teardown.
*
* Returns: a widget pointer to the newly created GtkWindow.
**/
GtkWidget*
gtk_test_create_simple_window (const gchar *window_title,
const gchar *dialog_text)
{
GtkWidget *window = gtk_test_create_widget (GTK_TYPE_WINDOW, "title", window_title, NULL);
GtkWidget *vbox = gtk_test_create_widget (GTK_TYPE_VBOX, "parent", window, NULL);
gtk_test_create_widget (GTK_TYPE_LABEL, "label", dialog_text, "parent", vbox, NULL);
g_signal_connect (window, "destroy", G_CALLBACK (try_main_quit), NULL);
gtk_widget_show_all (vbox);
return window;
}
/**
* gtk_test_xserver_render_sync
* @window: a mapped GdkWindow
*
* This function retrives a pixel from @window to force the windowing
* system to carry out any pending rendering commands.
* This function is intended to be used to syncronize with rendering
* pipelines, to benchmark windowing system rendering operations.
**/
void
gtk_test_xserver_render_sync (GdkWindow *window)
{
static GdkImage *p1image = NULL;
/* syncronize to X drawing queue, see:
* http://mail.gnome.org/archives/gtk-devel-list/2006-October/msg00103.html
*/
p1image = gdk_drawable_copy_to_image (window, p1image, 0, 0, 0, 0, 1, 1);
}
/**
* gtk_test_register_all_types
*
* Force registration of all core Gtk+ and Gdk object types.
* This allowes to refer to any of those object types via
* g_type_from_name() after calling this function.
**/
void
gtk_test_register_all_types (void)
{
volatile GType vgt;
#include "gtktypefuncs.c"
}
#define __GTK_TEST_UTILS_C__
#include "gtkaliasdef.c"

80
gtk/gtktestutils.h Normal file
View File

@ -0,0 +1,80 @@
/* Gtk+ testing utilities
* Copyright (C) 2007 Tim Janik
*
* 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 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GTK_TEST_UTILS_H__
#define __GTK_TEST_UTILS_H__
#include <gtk/gtkwidget.h>
#include <gtk/gtkspinbutton.h>
G_BEGIN_DECLS;
/* --- Gtk+ Test Utility API --- */
void gtk_test_init (int *argcp,
char ***argvp,
...);
void gtk_test_register_all_types (void);
GtkWidget* gtk_test_find_widget (GtkWidget *widget,
const gchar *label_pattern,
GType widget_type);
GtkWidget* gtk_test_create_widget (GType widget_type,
const gchar *first_property_name,
...);
GtkWidget* gtk_test_create_simple_window (const gchar *window_title,
const gchar *dialog_text);
GtkWidget* gtk_test_display_button_window (const gchar *window_title,
const gchar *dialog_text,
...); /* NULL terminated list of (label, &int) pairs */
void gtk_test_xserver_render_sync (GdkWindow *window);
void gtk_test_slider_set_perc (GtkWidget *widget, /* GtkRange-alike */
double percentage);
double gtk_test_slider_get_value (GtkWidget *widget);
gboolean gtk_test_spin_button_click (GtkSpinButton *widget,
guint button,
gboolean upwards);
gboolean gtk_test_widget_click (GtkWidget *widget,
guint button,
GdkModifierType modifiers);
gboolean gtk_test_widget_send_key (GtkWidget *widget,
guint keyval,
GdkModifierType modifiers);
/* operate on GtkEntry, GtkText, GtkTextView or GtkLabel */
void gtk_test_text_set (GtkWidget *widget,
const gchar *string);
gchar* gtk_test_text_get (GtkWidget *widget);
/* --- Gtk+ Test low-level API --- */
GtkWidget* gtk_test_find_sibling (GtkWidget *base_widget,
GType widget_type);
GtkWidget* gtk_test_find_label (GtkWidget *widget,
const gchar *label_pattern);
gboolean gtk_test_simulate_key (GdkWindow *window,
gint x,
gint y,
guint keyval,
GdkModifierType modifiers,
gboolean press_or_release);
gboolean gtk_test_simulate_button (GdkWindow *window,
gint x,
gint y,
guint button, /*1..3*/
GdkModifierType modifiers,
gboolean press_or_release);
G_END_DECLS;
#endif /* __GTK_TEST_UTILS_H__ */