1 | #undef G_DISABLE_ASSERT |
2 | #undef G_LOG_DOMAIN |
3 | |
4 | #include <locale.h> |
5 | #include <string.h> |
6 | #include <stdio.h> |
7 | #include <glib.h> |
8 | |
9 | static int depth = 0; |
10 | static GString *string; |
11 | |
12 | static void |
13 | indent (int ) |
14 | { |
15 | int i = 0; |
16 | while (i < depth) |
17 | { |
18 | g_string_append (string, val: " " ); |
19 | ++i; |
20 | } |
21 | } |
22 | |
23 | static void |
24 | start_element_handler (GMarkupParseContext *context, |
25 | const gchar *element_name, |
26 | const gchar **attribute_names, |
27 | const gchar **attribute_values, |
28 | gpointer user_data, |
29 | GError **error) |
30 | { |
31 | int i; |
32 | |
33 | indent (extra: 0); |
34 | g_string_append_printf (string, format: "ELEMENT '%s'\n" , element_name); |
35 | |
36 | i = 0; |
37 | while (attribute_names[i] != NULL) |
38 | { |
39 | indent (extra: 1); |
40 | |
41 | g_string_append_printf (string, format: "%s=\"%s\"\n" , |
42 | attribute_names[i], |
43 | attribute_values[i]); |
44 | |
45 | ++i; |
46 | } |
47 | |
48 | ++depth; |
49 | } |
50 | |
51 | static void |
52 | end_element_handler (GMarkupParseContext *context, |
53 | const gchar *element_name, |
54 | gpointer user_data, |
55 | GError **error) |
56 | { |
57 | --depth; |
58 | indent (extra: 0); |
59 | g_string_append_printf (string, format: "END '%s'\n" , element_name); |
60 | } |
61 | |
62 | static void |
63 | text_handler (GMarkupParseContext *context, |
64 | const gchar *text, |
65 | gsize text_len, |
66 | gpointer user_data, |
67 | GError **error) |
68 | { |
69 | indent (extra: 0); |
70 | g_string_append_printf (string, format: "TEXT '%.*s'\n" , (int)text_len, text); |
71 | } |
72 | |
73 | |
74 | static void |
75 | passthrough_handler (GMarkupParseContext *context, |
76 | const gchar *passthrough_text, |
77 | gsize text_len, |
78 | gpointer user_data, |
79 | GError **error) |
80 | { |
81 | indent (extra: 0); |
82 | |
83 | g_string_append_printf (string, format: "PASS '%.*s'\n" , (int)text_len, passthrough_text); |
84 | } |
85 | |
86 | static void |
87 | error_handler (GMarkupParseContext *context, |
88 | GError *error, |
89 | gpointer user_data) |
90 | { |
91 | g_string_append_printf (string, format: "ERROR %s\n" , error->message); |
92 | } |
93 | |
94 | static const GMarkupParser parser = { |
95 | start_element_handler, |
96 | end_element_handler, |
97 | text_handler, |
98 | passthrough_handler, |
99 | error_handler |
100 | }; |
101 | |
102 | static const GMarkupParser silent_parser = { |
103 | NULL, |
104 | NULL, |
105 | NULL, |
106 | NULL, |
107 | error_handler |
108 | }; |
109 | |
110 | static int |
111 | test_in_chunks (const gchar *contents, |
112 | gint length, |
113 | gint chunk_size, |
114 | GMarkupParseFlags flags) |
115 | { |
116 | GMarkupParseContext *context; |
117 | int i = 0; |
118 | |
119 | context = g_markup_parse_context_new (parser: &silent_parser, flags, NULL, NULL); |
120 | |
121 | while (i < length) |
122 | { |
123 | int this_chunk = MIN (length - i, chunk_size); |
124 | |
125 | if (!g_markup_parse_context_parse (context, |
126 | text: contents + i, |
127 | text_len: this_chunk, |
128 | NULL)) |
129 | { |
130 | g_markup_parse_context_free (context); |
131 | return 1; |
132 | } |
133 | |
134 | i += this_chunk; |
135 | } |
136 | |
137 | if (!g_markup_parse_context_end_parse (context, NULL)) |
138 | { |
139 | g_markup_parse_context_free (context); |
140 | return 1; |
141 | } |
142 | |
143 | g_markup_parse_context_free (context); |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | /* Load the given @filename and parse it multiple times with different chunking |
149 | * and length handling. All results should be equal. %TRUE is returned if the |
150 | * file was parsed successfully on every attempt; %FALSE if it failed to parse |
151 | * on every attempt. The test aborts if some attempts succeed and some fail. */ |
152 | static gboolean |
153 | test_file (const gchar *filename, |
154 | GMarkupParseFlags flags) |
155 | { |
156 | gchar *contents = NULL, *contents_unterminated = NULL; |
157 | gsize length_bytes; |
158 | GError *local_error = NULL; |
159 | GMarkupParseContext *context; |
160 | gint line, col; |
161 | guint n_failures = 0; |
162 | guint n_tests = 0; |
163 | const gsize chunk_sizes_bytes[] = { 1, 2, 5, 12, 1024 }; |
164 | gsize i; |
165 | GString *first_string = NULL; |
166 | |
167 | g_file_get_contents (filename, contents: &contents, length: &length_bytes, error: &local_error); |
168 | g_assert_no_error (local_error); |
169 | |
170 | /* Make a copy of the contents with no trailing nul. */ |
171 | contents_unterminated = g_malloc (n_bytes: length_bytes); |
172 | if (contents_unterminated != NULL) |
173 | memcpy (dest: contents_unterminated, src: contents, n: length_bytes); |
174 | |
175 | /* Test with nul termination. */ |
176 | context = g_markup_parse_context_new (parser: &parser, flags, NULL, NULL); |
177 | g_assert (g_markup_parse_context_get_user_data (context) == NULL); |
178 | g_markup_parse_context_get_position (context, line_number: &line, char_number: &col); |
179 | g_assert_cmpint (line, ==, 1); |
180 | g_assert_cmpint (col, ==, 1); |
181 | |
182 | if (!g_markup_parse_context_parse (context, text: contents, text_len: -1, NULL) || |
183 | !g_markup_parse_context_end_parse (context, NULL)) |
184 | n_failures++; |
185 | n_tests++; |
186 | |
187 | g_markup_parse_context_free (context); |
188 | |
189 | /* FIXME: Swap out the error string so we only return one copy of it, not |
190 | * @n_tests copies. This should be fixed properly by eliminating the global |
191 | * state in this file. */ |
192 | first_string = g_steal_pointer (&string); |
193 | string = g_string_new (init: "" ); |
194 | |
195 | /* With the length specified explicitly and a nul terminator present (since |
196 | * g_file_get_contents() always adds one). */ |
197 | if (test_in_chunks (contents, length: length_bytes, chunk_size: length_bytes, flags) != 0) |
198 | n_failures++; |
199 | n_tests++; |
200 | |
201 | /* With the length specified explicitly and no nul terminator present. */ |
202 | if (test_in_chunks (contents: contents_unterminated, length: length_bytes, chunk_size: length_bytes, flags) != 0) |
203 | n_failures++; |
204 | n_tests++; |
205 | |
206 | /* In various sized chunks. */ |
207 | for (i = 0; i < G_N_ELEMENTS (chunk_sizes_bytes); i++) |
208 | { |
209 | if (test_in_chunks (contents, length: length_bytes, chunk_size: chunk_sizes_bytes[i], flags) != 0) |
210 | n_failures++; |
211 | n_tests++; |
212 | } |
213 | |
214 | g_free (mem: contents); |
215 | g_free (mem: contents_unterminated); |
216 | |
217 | /* FIXME: Restore the error string. */ |
218 | g_string_free (string, TRUE); |
219 | string = g_steal_pointer (&first_string); |
220 | |
221 | /* We expect the file to either always be parsed successfully, or never be |
222 | * parsed successfully. There’s a bug in GMarkup if it sometimes parses |
223 | * successfully depending on how you chunk or terminate the input. */ |
224 | if (n_failures > 0) |
225 | g_assert_cmpint (n_failures, ==, n_tests); |
226 | |
227 | return (n_failures == 0); |
228 | } |
229 | |
230 | static gchar * |
231 | get_expected_filename (const gchar *filename, |
232 | GMarkupParseFlags flags) |
233 | { |
234 | gchar *f, *p, *expected; |
235 | |
236 | f = g_strdup (str: filename); |
237 | p = strstr (haystack: f, needle: ".gmarkup" ); |
238 | if (p) |
239 | *p = 0; |
240 | if (flags == 0) |
241 | expected = g_strconcat (string1: f, ".expected" , NULL); |
242 | else if (flags == G_MARKUP_TREAT_CDATA_AS_TEXT) |
243 | expected = g_strconcat (string1: f, ".cdata-as-text" , NULL); |
244 | else |
245 | g_assert_not_reached (); |
246 | |
247 | g_free (mem: f); |
248 | |
249 | return expected; |
250 | } |
251 | |
252 | static void |
253 | test_parse (gconstpointer d) |
254 | { |
255 | const gchar *filename = d; |
256 | gchar *expected_file; |
257 | gchar *expected; |
258 | gboolean valid_input; |
259 | GError *error = NULL; |
260 | gboolean res; |
261 | |
262 | valid_input = strstr (haystack: filename, needle: "valid" ) != NULL; |
263 | expected_file = get_expected_filename (filename, flags: 0); |
264 | |
265 | depth = 0; |
266 | string = g_string_sized_new (dfl_size: 0); |
267 | |
268 | res = test_file (filename, flags: 0); |
269 | g_assert_cmpint (res, ==, valid_input); |
270 | |
271 | g_file_get_contents (filename: expected_file, contents: &expected, NULL, error: &error); |
272 | g_assert_no_error (error); |
273 | g_assert_cmpstr (string->str, ==, expected); |
274 | g_free (mem: expected); |
275 | |
276 | g_string_free (string, TRUE); |
277 | |
278 | g_free (mem: expected_file); |
279 | |
280 | expected_file = get_expected_filename (filename, flags: G_MARKUP_TREAT_CDATA_AS_TEXT); |
281 | if (g_file_test (filename: expected_file, test: G_FILE_TEST_EXISTS)) |
282 | { |
283 | depth = 0; |
284 | string = g_string_sized_new (dfl_size: 0); |
285 | |
286 | res = test_file (filename, flags: G_MARKUP_TREAT_CDATA_AS_TEXT); |
287 | g_assert_cmpint (res, ==, valid_input); |
288 | |
289 | g_file_get_contents (filename: expected_file, contents: &expected, NULL, error: &error); |
290 | g_assert_no_error (error); |
291 | g_assert_cmpstr (string->str, ==, expected); |
292 | g_free (mem: expected); |
293 | |
294 | g_string_free (string, TRUE); |
295 | } |
296 | |
297 | g_free (mem: expected_file); |
298 | } |
299 | |
300 | int |
301 | main (int argc, char *argv[]) |
302 | { |
303 | GDir *dir; |
304 | GError *error; |
305 | const gchar *name; |
306 | gchar *path; |
307 | |
308 | g_setenv (variable: "LC_ALL" , value: "C" , TRUE); |
309 | setlocale (LC_ALL, locale: "" ); |
310 | |
311 | g_test_init (argc: &argc, argv: &argv, NULL); |
312 | |
313 | /* allow to easily generate expected output for new test cases */ |
314 | if (argc > 1) |
315 | { |
316 | gint arg = 1; |
317 | GMarkupParseFlags flags = 0; |
318 | |
319 | if (strcmp (s1: argv[1], s2: "--cdata-as-text" ) == 0) |
320 | { |
321 | flags = G_MARKUP_TREAT_CDATA_AS_TEXT; |
322 | arg = 2; |
323 | } |
324 | string = g_string_sized_new (dfl_size: 0); |
325 | test_file (filename: argv[arg], flags); |
326 | g_print (format: "%s" , string->str); |
327 | return 0; |
328 | } |
329 | |
330 | error = NULL; |
331 | path = g_test_build_filename (file_type: G_TEST_DIST, first_path: "markups" , NULL); |
332 | dir = g_dir_open (path, flags: 0, error: &error); |
333 | g_free (mem: path); |
334 | g_assert_no_error (error); |
335 | while ((name = g_dir_read_name (dir)) != NULL) |
336 | { |
337 | if (!strstr (haystack: name, needle: "gmarkup" )) |
338 | continue; |
339 | |
340 | path = g_strdup_printf (format: "/markup/parse/%s" , name); |
341 | g_test_add_data_func_full (testpath: path, test_data: g_test_build_filename (file_type: G_TEST_DIST, first_path: "markups" , name, NULL), |
342 | test_func: test_parse, data_free_func: g_free); |
343 | g_free (mem: path); |
344 | } |
345 | g_dir_close (dir); |
346 | |
347 | return g_test_run (); |
348 | } |
349 | |