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