]> Pileus Git - ~andy/gtk/blob - tests/reftests/gtk-reftest.c
492031bc4b4f078d6484c129c683d23d52ebd557
[~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 FALSE;
202 }
203
204 static cairo_surface_t *
205 snapshot_widget (GtkWidget *widget, SnapshotMode mode)
206 {
207   cairo_surface_t *surface;
208   cairo_pattern_t *bg;
209   GMainLoop *loop;
210   cairo_t *cr;
211
212   g_assert (gtk_widget_get_realized (widget));
213
214   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
215                                                CAIRO_CONTENT_COLOR,
216                                                gtk_widget_get_allocated_width (widget),
217                                                gtk_widget_get_allocated_height (widget));
218
219   loop = g_main_loop_new (NULL, FALSE);
220   g_idle_add (quit_when_idle, loop);
221   g_main_loop_run (loop);
222
223   cr = cairo_create (surface);
224
225   switch (mode)
226     {
227     case SNAPSHOT_WINDOW:
228       gdk_cairo_set_source_window (cr, gtk_widget_get_window (widget), 0, 0);
229       cairo_paint (cr);
230       break;
231     case SNAPSHOT_DRAW:
232       bg = gdk_window_get_background_pattern (gtk_widget_get_window (widget));
233       if (bg)
234         {
235           cairo_set_source (cr, bg);
236           cairo_paint (cr);
237         }
238       gtk_widget_draw (widget, cr);
239       break;
240     default:
241       g_assert_not_reached();
242       break;
243     }
244
245   cairo_destroy (cr);
246   g_main_loop_unref (loop);
247   gtk_widget_destroy (widget);
248
249   return surface;
250 }
251
252 static cairo_surface_t *
253 snapshot_ui_file (const char *ui_file)
254 {
255   GtkWidget *window;
256   GtkBuilder *builder;
257   GError *error = NULL;
258
259   builder = gtk_builder_new ();
260   gtk_builder_add_from_file (builder, ui_file, &error);
261   g_assert_no_error (error);
262   window = builder_get_toplevel (builder);
263   g_object_unref (builder);
264   g_assert (window);
265
266   gtk_widget_show (window);
267
268   return snapshot_widget (window, SNAPSHOT_WINDOW);
269 }
270
271 static void
272 save_image (cairo_surface_t *surface,
273             const char      *test_name,
274             const char      *extension)
275 {
276   char *filename = get_output_file (test_name, extension);
277
278   g_test_message ("Storing test result image at %s", filename);
279   g_assert (cairo_surface_write_to_png (surface, filename) == CAIRO_STATUS_SUCCESS);
280
281   g_free (filename);
282 }
283
284 static void
285 get_surface_size (cairo_surface_t *surface,
286                   int             *width,
287                   int             *height)
288 {
289   GdkRectangle area;
290   cairo_t *cr;
291
292   cr = cairo_create (surface);
293   if (!gdk_cairo_get_clip_rectangle (cr, &area))
294     {
295       g_assert_not_reached ();
296     }
297
298   g_assert (area.x == 0 && area.y == 0);
299   g_assert (area.width > 0 && area.height > 0);
300
301   *width = area.width;
302   *height = area.height;
303 }
304
305 static cairo_surface_t *
306 coerce_surface_for_comparison (cairo_surface_t *surface,
307                                int              width,
308                                int              height)
309 {
310   cairo_surface_t *coerced;
311   cairo_t *cr;
312
313   coerced = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
314                                         width,
315                                         height);
316   cr = cairo_create (coerced);
317   
318   cairo_set_source_surface (cr, surface, 0, 0);
319   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
320   cairo_paint (cr);
321
322   cairo_destroy (cr);
323   cairo_surface_destroy (surface);
324
325   g_assert (cairo_surface_status (coerced) == CAIRO_STATUS_SUCCESS);
326
327   return coerced;
328 }
329
330 /* Compares two CAIRO_FORMAT_ARGB32 buffers, returning NULL if the
331  * buffers are equal or a surface containing a diff between the two
332  * surfaces.
333  *
334  * This function should be rewritten to compare all formats supported by
335  * cairo_format_t instead of taking a mask as a parameter.
336  *
337  * This function is originally from cairo:test/buffer-diff.c.
338  * Copyright Â© 2004 Richard D. Worth
339  */
340 static cairo_surface_t *
341 buffer_diff_core (const guchar *buf_a,
342                   int           stride_a,
343                   const guchar *buf_b,
344                   int           stride_b,
345                   int           width,
346                   int           height)
347 {
348   int x, y;
349   guchar *buf_diff = NULL;
350   int stride_diff = 0;
351   cairo_surface_t *diff = NULL;
352
353   for (y = 0; y < height; y++)
354     {
355       const guint32 *row_a = (const guint32 *) (buf_a + y * stride_a);
356       const guint32 *row_b = (const guint32 *) (buf_b + y * stride_b);
357       guint32 *row = (guint32 *) (buf_diff + y * stride_diff);
358
359       for (x = 0; x < width; x++)
360         {
361           int channel;
362           guint32 diff_pixel = 0;
363
364           /* check if the pixels are the same */
365           if (row_a[x] == row_b[x])
366             continue;
367         
368           if (diff == NULL)
369             {
370               diff = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
371                                                  width,
372                                                  height);
373               g_assert (cairo_surface_status (diff) == CAIRO_STATUS_SUCCESS);
374               buf_diff = cairo_image_surface_get_data (diff);
375               stride_diff = cairo_image_surface_get_stride (diff);
376               row = (guint32 *) (buf_diff + y * stride_diff);
377             }
378
379           /* calculate a difference value for all 4 channels */
380           for (channel = 0; channel < 4; channel++)
381             {
382               int value_a = (row_a[x] >> (channel*8)) & 0xff;
383               int value_b = (row_b[x] >> (channel*8)) & 0xff;
384               guint diff;
385
386               diff = ABS (value_a - value_b);
387               diff *= 4;  /* emphasize */
388               if (diff)
389                 diff += 128; /* make sure it's visible */
390               if (diff > 255)
391                 diff = 255;
392               diff_pixel |= diff << (channel*8);
393             }
394
395           if ((diff_pixel & 0x00ffffff) == 0)
396             {
397               /* alpha only difference, convert to luminance */
398               guint8 alpha = diff_pixel >> 24;
399               diff_pixel = alpha * 0x010101;
400             }
401           
402           row[x] = diff_pixel;
403       }
404   }
405
406   return diff;
407 }
408
409 static cairo_surface_t *
410 compare_surfaces (const char *test_file,
411                   cairo_surface_t *surface1,
412                   cairo_surface_t *surface2)
413 {
414   int w1, h1, w2, h2, w, h;
415   cairo_surface_t *diff;
416   
417   get_surface_size (surface1, &w1, &h1);
418   get_surface_size (surface2, &w2, &h2);
419   w = MAX (w1, w2);
420   h = MAX (h1, h2);
421   surface1 = coerce_surface_for_comparison (surface1, w, h);
422   surface2 = coerce_surface_for_comparison (surface2, w, h);
423
424   diff = buffer_diff_core (cairo_image_surface_get_data (surface1),
425                            cairo_image_surface_get_stride (surface1),
426                            cairo_image_surface_get_data (surface2),
427                            cairo_image_surface_get_stride (surface2),
428                            w, h);
429
430   return diff;
431 }
432
433 static void
434 test_ui_file (GFile *file)
435 {
436   char *ui_file, *reference_file;
437   cairo_surface_t *ui_image, *reference_image, *diff_image;
438   GtkStyleProvider *provider;
439
440   ui_file = g_file_get_path (file);
441
442   provider = add_extra_css (ui_file, ".css");
443
444   ui_image = snapshot_ui_file (ui_file);
445   
446   reference_file = get_test_file (ui_file, ".ref.ui", TRUE);
447   if (reference_file)
448     reference_image = snapshot_ui_file (reference_file);
449   else
450     {
451       reference_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
452       g_test_message ("No reference image.");
453       g_test_fail ();
454     }
455   g_free (reference_file);
456
457   diff_image = compare_surfaces (ui_file, ui_image, reference_image);
458
459   save_image (ui_image, ui_file, ".out.png");
460   save_image (reference_image, ui_file, ".ref.png");
461   if (diff_image)
462     {
463       save_image (diff_image, ui_file, ".diff.png");
464       g_test_fail ();
465     }
466
467   remove_extra_css (provider);
468 }
469
470 static void
471 add_test_for_file (GFile *file)
472 {
473   g_test_add_vtable (g_file_get_path (file),
474                      0,
475                      g_object_ref (file),
476                      NULL,
477                      (GTestFixtureFunc) test_ui_file,
478                      (GTestFixtureFunc) g_object_unref);
479 }
480
481 static int
482 compare_files (gconstpointer a, gconstpointer b)
483 {
484   GFile *file1 = G_FILE (a);
485   GFile *file2 = G_FILE (b);
486   char *path1, *path2;
487   int result;
488
489   path1 = g_file_get_path (file1);
490   path2 = g_file_get_path (file2);
491
492   result = strcmp (path1, path2);
493
494   g_free (path1);
495   g_free (path2);
496
497   return result;
498 }
499
500 static void
501 add_tests_for_files_in_directory (GFile *dir)
502 {
503   GFileEnumerator *enumerator;
504   GFileInfo *info;
505   GList *files;
506   GError *error = NULL;
507
508   enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
509   g_assert_no_error (error);
510   files = NULL;
511
512   while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)))
513     {
514       const char *filename;
515
516       filename = g_file_info_get_name (info);
517
518       if (!g_str_has_suffix (filename, ".ui") ||
519           g_str_has_suffix (filename, ".ref.ui"))
520         {
521           g_object_unref (info);
522           continue;
523         }
524
525       files = g_list_prepend (files, g_file_get_child (dir, filename));
526
527       g_object_unref (info);
528     }
529   
530   g_assert_no_error (error);
531   g_object_unref (enumerator);
532
533   files = g_list_sort (files, compare_files);
534   g_list_foreach (files, (GFunc) add_test_for_file, NULL);
535   g_list_free_full (files, g_object_unref);
536 }
537
538 int
539 main (int argc, char **argv)
540 {
541   if (!parse_command_line (&argc, &argv))
542     return 1;
543
544   if (argc < 2)
545     {
546       const char *basedir;
547       GFile *dir;
548
549       if (g_getenv ("srcdir"))
550         basedir = g_getenv ("srcdir");
551       else
552         basedir = ".";
553         
554       dir = g_file_new_for_path (basedir);
555       
556       add_tests_for_files_in_directory (dir);
557
558       g_object_unref (dir);
559     }
560   else
561     {
562       guint i;
563
564       for (i = 1; i < argc; i++)
565         {
566           GFile *file = g_file_new_for_commandline_arg (argv[i]);
567
568           add_test_for_file (file);
569
570           g_object_unref (file);
571         }
572     }
573
574   return g_test_run ();
575 }
576