]> Pileus Git - ~andy/gtk/blob - tests/reftests/gtk-reftest.c
Use G_SOURCE_CONTINUE/REMOVE
[~andy/gtk] / tests / reftests / gtk-reftest.c
1 /*
2  * Copyright (C) 2011 Red Hat Inc.
3  *
4  * Author:
5  *      Benjamin Otte <otte@gnome.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "config.h"
24
25 #include <string.h>
26 #include <glib/gstdio.h>
27 #include <gtk/gtk.h>
28
29 typedef enum {
30   SNAPSHOT_WINDOW,
31   SNAPSHOT_DRAW
32 } SnapshotMode;
33
34 /* This is exactly the style information you've been looking for */
35 #define GTK_STYLE_PROVIDER_PRIORITY_FORCE G_MAXUINT
36
37 static char *arg_output_dir = NULL;
38
39 static const GOptionEntry test_args[] = {
40   { "output",         'o', 0, G_OPTION_ARG_FILENAME, &arg_output_dir,
41     "Directory to save image files to", "DIR" },
42   { NULL }
43 };
44
45 static gboolean
46 parse_command_line (int *argc, char ***argv)
47 {
48   GError *error = NULL;
49   GOptionContext *context;
50
51   context = g_option_context_new ("- run GTK reftests");
52   g_option_context_add_main_entries (context, test_args, NULL);
53   g_option_context_set_ignore_unknown_options (context, TRUE);
54
55   if (!g_option_context_parse (context, argc, argv, &error))
56     {
57       g_print ("option parsing failed: %s\n", error->message);
58       return FALSE;
59     }
60
61   gtk_test_init (argc, argv);
62
63   return TRUE;
64 }
65
66 static const char *
67 get_output_dir (void)
68 {
69   static const char *output_dir = NULL;
70   GError *error = NULL;
71
72   if (output_dir)
73     return output_dir;
74
75   if (arg_output_dir)
76     {
77       GFile *file = g_file_new_for_commandline_arg (arg_output_dir);
78       output_dir = g_file_get_path (file);
79       g_object_unref (file);
80     }
81   else
82     {
83       output_dir = g_get_tmp_dir ();
84     }
85
86   if (!g_file_test (output_dir, G_FILE_TEST_EXISTS))
87     {
88       GFile *file;
89
90       file = g_file_new_for_path (output_dir);
91       g_assert (g_file_make_directory_with_parents (file, NULL, &error));
92       g_assert_no_error (error);
93       g_object_unref (file);
94     }
95
96   return output_dir;
97 }
98
99 static char *
100 get_output_file (const char *test_file,
101                  const char *extension)
102 {
103   const char *output_dir = get_output_dir ();
104   char *result, *base;
105
106   base = g_path_get_basename (test_file);
107   if (g_str_has_suffix (base, ".ui"))
108     base[strlen (base) - strlen (".ui")] = '\0';
109
110   result = g_strconcat (output_dir, G_DIR_SEPARATOR_S, base, extension, NULL);
111   g_free (base);
112
113   return result;
114 }
115
116 static char *
117 get_test_file (const char *test_file,
118                const char *extension,
119                gboolean    must_exist)
120 {
121   GString *file = g_string_new (NULL);
122
123   if (g_str_has_suffix (test_file, ".ui"))
124     g_string_append_len (file, test_file, strlen (test_file) - strlen (".ui"));
125   else
126     g_string_append (file, test_file);
127   
128   g_string_append (file, extension);
129
130   if (must_exist &&
131       !g_file_test (file->str, G_FILE_TEST_EXISTS))
132     {
133       g_string_free (file, TRUE);
134       return NULL;
135     }
136
137   return g_string_free (file, FALSE);
138 }
139
140 static GtkStyleProvider *
141 add_extra_css (const char *testname,
142                const char *extension)
143 {
144   GtkStyleProvider *provider = NULL;
145   char *css_file;
146   
147   css_file = get_test_file (testname, extension, TRUE);
148   if (css_file == NULL)
149     return NULL;
150
151   provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
152   gtk_css_provider_load_from_path (GTK_CSS_PROVIDER (provider),
153                                    css_file,
154                                    NULL);
155   gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
156                                              provider,
157                                              GTK_STYLE_PROVIDER_PRIORITY_FORCE);
158
159   g_free (css_file);
160   
161   return provider;
162 }
163
164 static void
165 remove_extra_css (GtkStyleProvider *provider)
166 {
167   if (provider == NULL)
168     return;
169
170   gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
171                                                 provider);
172 }
173
174 static GtkWidget *
175 builder_get_toplevel (GtkBuilder *builder)
176 {
177   GSList *list, *walk;
178   GtkWidget *window = NULL;
179
180   list = gtk_builder_get_objects (builder);
181   for (walk = list; walk; walk = walk->next)
182     {
183       if (GTK_IS_WINDOW (walk->data) &&
184           gtk_widget_get_parent (walk->data) == NULL)
185         {
186           window = walk->data;
187           break;
188         }
189     }
190   
191   g_slist_free (list);
192
193   return window;
194 }
195
196 static gboolean
197 quit_when_idle (gpointer loop)
198 {
199   g_main_loop_quit (loop);
200
201   return G_SOURCE_REMOVE;
202 }
203
204 static void
205 check_for_draw (GdkEvent *event, gpointer loop)
206 {
207   if (event->type == GDK_EXPOSE)
208     {
209       g_idle_add (quit_when_idle, loop);
210       gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL);
211     }
212
213   gtk_main_do_event (event);
214 }
215
216 static cairo_surface_t *
217 snapshot_widget (GtkWidget *widget, SnapshotMode mode)
218 {
219   cairo_surface_t *surface;
220   cairo_pattern_t *bg;
221   GMainLoop *loop;
222   cairo_t *cr;
223
224   g_assert (gtk_widget_get_realized (widget));
225
226   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
227                                                CAIRO_CONTENT_COLOR,
228                                                gtk_widget_get_allocated_width (widget),
229                                                gtk_widget_get_allocated_height (widget));
230
231   loop = g_main_loop_new (NULL, FALSE);
232   /* We wait until the widget is drawn for the first time.
233    * We can not wait for a GtkWidget::draw event, because that might not
234    * happen if the window is fully obscured by windowed child widgets.
235    * Alternatively, we could wait for an expose event on widget's window.
236    * Both of these are rather hairy, not sure what's best. */
237   gdk_event_handler_set (check_for_draw, loop, NULL);
238   g_main_loop_run (loop);
239
240   cr = cairo_create (surface);
241
242   switch (mode)
243     {
244     case SNAPSHOT_WINDOW:
245       {
246         GdkWindow *window = gtk_widget_get_window (widget);
247         if (gdk_window_get_window_type (window) == GDK_WINDOW_TOPLEVEL ||
248             gdk_window_get_window_type (window) == GDK_WINDOW_FOREIGN)
249           {
250             /* give the WM/server some time to sync. They need it.
251              * Also, do use popups instead of toplevls in your tests
252              * whenever you can. */
253             gdk_display_sync (gdk_window_get_display (window));
254             g_timeout_add (500, quit_when_idle, loop);
255             g_main_loop_run (loop);
256           }
257         gdk_cairo_set_source_window (cr, window, 0, 0);
258         cairo_paint (cr);
259       }
260       break;
261     case SNAPSHOT_DRAW:
262       bg = gdk_window_get_background_pattern (gtk_widget_get_window (widget));
263       if (bg)
264         {
265           cairo_set_source (cr, bg);
266           cairo_paint (cr);
267         }
268       gtk_widget_draw (widget, cr);
269       break;
270     default:
271       g_assert_not_reached();
272       break;
273     }
274
275   cairo_destroy (cr);
276   g_main_loop_unref (loop);
277   gtk_widget_destroy (widget);
278
279   return surface;
280 }
281
282 static cairo_surface_t *
283 snapshot_ui_file (const char *ui_file)
284 {
285   GtkWidget *window;
286   GtkBuilder *builder;
287   GError *error = NULL;
288
289   builder = gtk_builder_new ();
290   gtk_builder_add_from_file (builder, ui_file, &error);
291   g_assert_no_error (error);
292   window = builder_get_toplevel (builder);
293   g_object_unref (builder);
294   g_assert (window);
295
296   gtk_widget_show (window);
297
298   return snapshot_widget (window, SNAPSHOT_WINDOW);
299 }
300
301 static void
302 save_image (cairo_surface_t *surface,
303             const char      *test_name,
304             const char      *extension)
305 {
306   char *filename = get_output_file (test_name, extension);
307
308   g_test_message ("Storing test result image at %s", filename);
309   g_assert (cairo_surface_write_to_png (surface, filename) == CAIRO_STATUS_SUCCESS);
310
311   g_free (filename);
312 }
313
314 static void
315 get_surface_size (cairo_surface_t *surface,
316                   int             *width,
317                   int             *height)
318 {
319   GdkRectangle area;
320   cairo_t *cr;
321
322   cr = cairo_create (surface);
323   if (!gdk_cairo_get_clip_rectangle (cr, &area))
324     {
325       g_assert_not_reached ();
326     }
327
328   g_assert (area.x == 0 && area.y == 0);
329   g_assert (area.width > 0 && area.height > 0);
330
331   *width = area.width;
332   *height = area.height;
333 }
334
335 static cairo_surface_t *
336 coerce_surface_for_comparison (cairo_surface_t *surface,
337                                int              width,
338                                int              height)
339 {
340   cairo_surface_t *coerced;
341   cairo_t *cr;
342
343   coerced = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
344                                         width,
345                                         height);
346   cr = cairo_create (coerced);
347   
348   cairo_set_source_surface (cr, surface, 0, 0);
349   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
350   cairo_paint (cr);
351
352   cairo_destroy (cr);
353   cairo_surface_destroy (surface);
354
355   g_assert (cairo_surface_status (coerced) == CAIRO_STATUS_SUCCESS);
356
357   return coerced;
358 }
359
360 /* Compares two CAIRO_FORMAT_ARGB32 buffers, returning NULL if the
361  * buffers are equal or a surface containing a diff between the two
362  * surfaces.
363  *
364  * This function should be rewritten to compare all formats supported by
365  * cairo_format_t instead of taking a mask as a parameter.
366  *
367  * This function is originally from cairo:test/buffer-diff.c.
368  * Copyright Â© 2004 Richard D. Worth
369  */
370 static cairo_surface_t *
371 buffer_diff_core (const guchar *buf_a,
372                   int           stride_a,
373                   const guchar *buf_b,
374                   int           stride_b,
375                   int           width,
376                   int           height)
377 {
378   int x, y;
379   guchar *buf_diff = NULL;
380   int stride_diff = 0;
381   cairo_surface_t *diff = NULL;
382
383   for (y = 0; y < height; y++)
384     {
385       const guint32 *row_a = (const guint32 *) (buf_a + y * stride_a);
386       const guint32 *row_b = (const guint32 *) (buf_b + y * stride_b);
387       guint32 *row = (guint32 *) (buf_diff + y * stride_diff);
388
389       for (x = 0; x < width; x++)
390         {
391           int channel;
392           guint32 diff_pixel = 0;
393
394           /* check if the pixels are the same */
395           if (row_a[x] == row_b[x])
396             continue;
397         
398           if (diff == NULL)
399             {
400               diff = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
401                                                  width,
402                                                  height);
403               g_assert (cairo_surface_status (diff) == CAIRO_STATUS_SUCCESS);
404               buf_diff = cairo_image_surface_get_data (diff);
405               stride_diff = cairo_image_surface_get_stride (diff);
406               row = (guint32 *) (buf_diff + y * stride_diff);
407             }
408
409           /* calculate a difference value for all 4 channels */
410           for (channel = 0; channel < 4; channel++)
411             {
412               int value_a = (row_a[x] >> (channel*8)) & 0xff;
413               int value_b = (row_b[x] >> (channel*8)) & 0xff;
414               guint diff;
415
416               diff = ABS (value_a - value_b);
417               diff *= 4;  /* emphasize */
418               if (diff)
419                 diff += 128; /* make sure it's visible */
420               if (diff > 255)
421                 diff = 255;
422               diff_pixel |= diff << (channel*8);
423             }
424
425           if ((diff_pixel & 0x00ffffff) == 0)
426             {
427               /* alpha only difference, convert to luminance */
428               guint8 alpha = diff_pixel >> 24;
429               diff_pixel = alpha * 0x010101;
430             }
431           
432           row[x] = diff_pixel;
433       }
434   }
435
436   return diff;
437 }
438
439 static cairo_surface_t *
440 compare_surfaces (const char *test_file,
441                   cairo_surface_t *surface1,
442                   cairo_surface_t *surface2)
443 {
444   int w1, h1, w2, h2, w, h;
445   cairo_surface_t *diff;
446   
447   get_surface_size (surface1, &w1, &h1);
448   get_surface_size (surface2, &w2, &h2);
449   w = MAX (w1, w2);
450   h = MAX (h1, h2);
451   surface1 = coerce_surface_for_comparison (surface1, w, h);
452   surface2 = coerce_surface_for_comparison (surface2, w, h);
453
454   diff = buffer_diff_core (cairo_image_surface_get_data (surface1),
455                            cairo_image_surface_get_stride (surface1),
456                            cairo_image_surface_get_data (surface2),
457                            cairo_image_surface_get_stride (surface2),
458                            w, h);
459
460   return diff;
461 }
462
463 static void
464 test_ui_file (GFile *file)
465 {
466   char *ui_file, *reference_file;
467   cairo_surface_t *ui_image, *reference_image, *diff_image;
468   GtkStyleProvider *provider;
469
470   ui_file = g_file_get_path (file);
471
472   provider = add_extra_css (ui_file, ".css");
473
474   ui_image = snapshot_ui_file (ui_file);
475   
476   reference_file = get_test_file (ui_file, ".ref.ui", TRUE);
477   if (reference_file)
478     reference_image = snapshot_ui_file (reference_file);
479   else
480     {
481       reference_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
482       g_test_message ("No reference image.");
483       g_test_fail ();
484     }
485   g_free (reference_file);
486
487   diff_image = compare_surfaces (ui_file, ui_image, reference_image);
488
489   save_image (ui_image, ui_file, ".out.png");
490   save_image (reference_image, ui_file, ".ref.png");
491   if (diff_image)
492     {
493       save_image (diff_image, ui_file, ".diff.png");
494       g_test_fail ();
495     }
496
497   remove_extra_css (provider);
498 }
499
500 static void
501 add_test_for_file (GFile *file)
502 {
503   g_test_add_vtable (g_file_get_path (file),
504                      0,
505                      g_object_ref (file),
506                      NULL,
507                      (GTestFixtureFunc) test_ui_file,
508                      (GTestFixtureFunc) g_object_unref);
509 }
510
511 static int
512 compare_files (gconstpointer a, gconstpointer b)
513 {
514   GFile *file1 = G_FILE (a);
515   GFile *file2 = G_FILE (b);
516   char *path1, *path2;
517   int result;
518
519   path1 = g_file_get_path (file1);
520   path2 = g_file_get_path (file2);
521
522   result = strcmp (path1, path2);
523
524   g_free (path1);
525   g_free (path2);
526
527   return result;
528 }
529
530 static void
531 add_tests_for_files_in_directory (GFile *dir)
532 {
533   GFileEnumerator *enumerator;
534   GFileInfo *info;
535   GList *files;
536   GError *error = NULL;
537
538   enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
539   g_assert_no_error (error);
540   files = NULL;
541
542   while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)))
543     {
544       const char *filename;
545
546       filename = g_file_info_get_name (info);
547
548       if (!g_str_has_suffix (filename, ".ui") ||
549           g_str_has_suffix (filename, ".ref.ui"))
550         {
551           g_object_unref (info);
552           continue;
553         }
554
555       files = g_list_prepend (files, g_file_get_child (dir, filename));
556
557       g_object_unref (info);
558     }
559   
560   g_assert_no_error (error);
561   g_object_unref (enumerator);
562
563   files = g_list_sort (files, compare_files);
564   g_list_foreach (files, (GFunc) add_test_for_file, NULL);
565   g_list_free_full (files, g_object_unref);
566 }
567
568 int
569 main (int argc, char **argv)
570 {
571   if (!parse_command_line (&argc, &argv))
572     return 1;
573
574   if (argc < 2)
575     {
576       const char *basedir;
577       GFile *dir;
578
579       if (g_getenv ("srcdir"))
580         basedir = g_getenv ("srcdir");
581       else
582         basedir = ".";
583         
584       dir = g_file_new_for_path (basedir);
585       
586       add_tests_for_files_in_directory (dir);
587
588       g_object_unref (dir);
589     }
590   else
591     {
592       guint i;
593
594       for (i = 1; i < argc; i++)
595         {
596           GFile *file = g_file_new_for_commandline_arg (argv[i]);
597
598           add_test_for_file (file);
599
600           g_object_unref (file);
601         }
602     }
603
604   return g_test_run ();
605 }
606