1 | #include <string.h> |
2 | #include <glib/gstdio.h> |
3 | #include <gtk/gtk.h> |
4 | #include <stdlib.h> |
5 | #include "../reftests/reftest-compare.h" |
6 | |
7 | static char *arg_output_dir = NULL; |
8 | |
9 | static const char * |
10 | get_output_dir (void) |
11 | { |
12 | static const char *output_dir = NULL; |
13 | GError *error = NULL; |
14 | GFile *file; |
15 | |
16 | if (output_dir) |
17 | return output_dir; |
18 | |
19 | if (arg_output_dir) |
20 | { |
21 | GFile *arg_file = g_file_new_for_commandline_arg (arg: arg_output_dir); |
22 | const char *subdir; |
23 | |
24 | subdir = g_getenv (variable: "TEST_OUTPUT_SUBDIR" ); |
25 | if (subdir) |
26 | { |
27 | GFile *child = g_file_get_child (file: arg_file, name: subdir); |
28 | g_object_unref (object: arg_file); |
29 | arg_file = child; |
30 | } |
31 | |
32 | output_dir = g_file_get_path (file: arg_file); |
33 | g_object_unref (object: arg_file); |
34 | } |
35 | else |
36 | { |
37 | output_dir = g_get_tmp_dir (); |
38 | } |
39 | |
40 | /* Just try to create the output directory. |
41 | * If it already exists, that's exactly what we wanted to check, |
42 | * so we can happily skip that error. |
43 | */ |
44 | file = g_file_new_for_path (path: output_dir); |
45 | if (!g_file_make_directory_with_parents (file, NULL, error: &error)) |
46 | { |
47 | g_object_unref (object: file); |
48 | |
49 | if (!g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_EXISTS)) |
50 | { |
51 | g_error ("Failed to create output dir: %s" , error->message); |
52 | g_error_free (error); |
53 | return NULL; |
54 | } |
55 | g_error_free (error); |
56 | } |
57 | else |
58 | g_object_unref (object: file); |
59 | |
60 | return output_dir; |
61 | } |
62 | |
63 | static char * |
64 | file_replace_extension (const char *old_file, |
65 | const char *old_ext, |
66 | const char *new_ext) |
67 | { |
68 | GString *file = g_string_new (NULL); |
69 | |
70 | if (g_str_has_suffix (str: old_file, suffix: old_ext)) |
71 | g_string_append_len (string: file, val: old_file, len: strlen (s: old_file) - strlen (s: old_ext)); |
72 | else |
73 | g_string_append (string: file, val: old_file); |
74 | |
75 | g_string_append (string: file, val: new_ext); |
76 | |
77 | return g_string_free (string: file, FALSE); |
78 | } |
79 | |
80 | static char * |
81 | get_output_file (const char *file, |
82 | const char *orig_ext, |
83 | const char *new_ext) |
84 | { |
85 | const char *dir; |
86 | char *result, *base; |
87 | char *name; |
88 | |
89 | dir = get_output_dir (); |
90 | base = g_path_get_basename (file_name: file); |
91 | name = file_replace_extension (old_file: base, old_ext: orig_ext, new_ext); |
92 | |
93 | result = g_strconcat (string1: dir, G_DIR_SEPARATOR_S, name, NULL); |
94 | |
95 | g_free (mem: base); |
96 | g_free (mem: name); |
97 | |
98 | return result; |
99 | } |
100 | |
101 | static void |
102 | save_image (GdkTexture *texture, |
103 | const char *test_name, |
104 | const char *extension) |
105 | { |
106 | char *filename = get_output_file (file: test_name, orig_ext: ".node" , new_ext: extension); |
107 | gboolean result; |
108 | |
109 | g_print (format: "Storing test result image at %s\n" , filename); |
110 | result = gdk_texture_save_to_png (texture, filename); |
111 | g_assert_true (result); |
112 | g_free (mem: filename); |
113 | } |
114 | |
115 | static void |
116 | deserialize_error_func (const GskParseLocation *start, |
117 | const GskParseLocation *end, |
118 | const GError *error, |
119 | gpointer user_data) |
120 | { |
121 | GString *string = g_string_new (init: "<data>" ); |
122 | |
123 | g_string_append_printf (string, format: ":%zu:%zu" , |
124 | start->lines + 1, start->line_chars + 1); |
125 | if (start->lines != end->lines || start->line_chars != end->line_chars) |
126 | { |
127 | g_string_append (string, val: "-" ); |
128 | if (start->lines != end->lines) |
129 | g_string_append_printf (string, format: "%zu:" , end->lines + 1); |
130 | g_string_append_printf (string, format: "%zu" , end->line_chars + 1); |
131 | } |
132 | |
133 | g_warning ("Error at %s: %s" , string->str, error->message); |
134 | |
135 | g_string_free (string, TRUE); |
136 | } |
137 | |
138 | static const GOptionEntry options[] = { |
139 | { "output" , 0, 0, G_OPTION_ARG_FILENAME, &arg_output_dir, |
140 | "Directory to save image files to" , "DIR" }, |
141 | { NULL } |
142 | }; |
143 | |
144 | /* |
145 | * Non-option arguments: |
146 | * 1) .node file to compare |
147 | * 2) .png file to compare the rendered .node file to |
148 | */ |
149 | int |
150 | main (int argc, char **argv) |
151 | { |
152 | GdkTexture *reference_texture; |
153 | GdkTexture *rendered_texture; |
154 | GskRenderer *renderer; |
155 | GdkSurface *window; |
156 | GskRenderNode *node; |
157 | const char *node_file; |
158 | const char *png_file; |
159 | gboolean success = TRUE; |
160 | GError *error = NULL; |
161 | GOptionContext *context; |
162 | |
163 | (g_test_init) (argc: &argc, argv: &argv, NULL); |
164 | |
165 | context = g_option_context_new (parameter_string: "NODE REF - run GSK node tests" ); |
166 | g_option_context_add_main_entries (context, entries: options, NULL); |
167 | g_option_context_set_ignore_unknown_options (context, TRUE); |
168 | |
169 | if (!g_option_context_parse (context, argc: &argc, argv: &argv, error: &error)) |
170 | { |
171 | g_error ("Option parsing failed: %s\n" , error->message); |
172 | return 1; |
173 | } |
174 | else if (argc != 3) |
175 | { |
176 | char *help = g_option_context_get_help (context, TRUE, NULL); |
177 | g_print (format: "%s" , help); |
178 | return 1; |
179 | } |
180 | |
181 | g_option_context_free (context); |
182 | |
183 | gtk_init (); |
184 | |
185 | node_file = argv[1]; |
186 | png_file = argv[2]; |
187 | |
188 | window = gdk_surface_new_toplevel (display: gdk_display_get_default()); |
189 | renderer = gsk_renderer_new_for_surface (surface: window); |
190 | |
191 | g_print (format: "Node file: '%s'\n" , node_file); |
192 | g_print (format: "PNG file: '%s'\n" , png_file); |
193 | |
194 | /* Load the render node from the given .node file */ |
195 | { |
196 | GBytes *bytes; |
197 | gsize len; |
198 | char *contents; |
199 | |
200 | if (!g_file_get_contents (filename: node_file, contents: &contents, length: &len, error: &error)) |
201 | { |
202 | g_print (format: "Could not open node file: %s\n" , error->message); |
203 | g_clear_error (err: &error); |
204 | return 1; |
205 | } |
206 | |
207 | bytes = g_bytes_new_take (data: contents, size: len); |
208 | node = gsk_render_node_deserialize (bytes, error_func: deserialize_error_func, user_data: &success); |
209 | g_bytes_unref (bytes); |
210 | |
211 | g_assert_no_error (error); |
212 | g_assert_nonnull (node); |
213 | } |
214 | |
215 | /* Render the .node file and download to cairo surface */ |
216 | rendered_texture = gsk_renderer_render_texture (renderer, root: node, NULL); |
217 | g_assert_nonnull (rendered_texture); |
218 | |
219 | /* Load the given reference png file */ |
220 | reference_texture = gdk_texture_new_from_filename (path: png_file, error: &error); |
221 | if (reference_texture == NULL) |
222 | { |
223 | g_print (format: "Error loading reference surface: %s\n" , error->message); |
224 | g_clear_error (err: &error); |
225 | success = FALSE; |
226 | } |
227 | else |
228 | { |
229 | GdkTexture *diff_texture; |
230 | |
231 | /* Now compare the two */ |
232 | diff_texture = reftest_compare_textures (texture1: rendered_texture, texture2: reference_texture); |
233 | |
234 | if (diff_texture) |
235 | { |
236 | save_image (texture: diff_texture, test_name: node_file, extension: ".diff.png" ); |
237 | g_object_unref (object: diff_texture); |
238 | success = FALSE; |
239 | } |
240 | } |
241 | |
242 | save_image (texture: rendered_texture, test_name: node_file, extension: ".out.png" ); |
243 | |
244 | g_object_unref (object: reference_texture); |
245 | g_object_unref (object: rendered_texture); |
246 | |
247 | gsk_render_node_unref (node); |
248 | |
249 | return success ? 0 : 1; |
250 | } |
251 | |