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
indent(int extra)13 indent (int extra)
14 {
15 int i = 0;
16 while (i < depth)
17 {
18 g_string_append (string, " ");
19 ++i;
20 }
21 }
22
23 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)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 (0);
34 g_string_append_printf (string, "ELEMENT '%s'\n", element_name);
35
36 i = 0;
37 while (attribute_names[i] != NULL)
38 {
39 indent (1);
40
41 g_string_append_printf (string, "%s=\"%s\"\n",
42 attribute_names[i],
43 attribute_values[i]);
44
45 ++i;
46 }
47
48 ++depth;
49 }
50
51 static void
end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)52 end_element_handler (GMarkupParseContext *context,
53 const gchar *element_name,
54 gpointer user_data,
55 GError **error)
56 {
57 --depth;
58 indent (0);
59 g_string_append_printf (string, "END '%s'\n", element_name);
60 }
61
62 static void
text_handler(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)63 text_handler (GMarkupParseContext *context,
64 const gchar *text,
65 gsize text_len,
66 gpointer user_data,
67 GError **error)
68 {
69 indent (0);
70 g_string_append_printf (string, "TEXT '%.*s'\n", (int)text_len, text);
71 }
72
73
74 static void
passthrough_handler(GMarkupParseContext * context,const gchar * passthrough_text,gsize text_len,gpointer user_data,GError ** error)75 passthrough_handler (GMarkupParseContext *context,
76 const gchar *passthrough_text,
77 gsize text_len,
78 gpointer user_data,
79 GError **error)
80 {
81 indent (0);
82
83 g_string_append_printf (string, "PASS '%.*s'\n", (int)text_len, passthrough_text);
84 }
85
86 static void
error_handler(GMarkupParseContext * context,GError * error,gpointer user_data)87 error_handler (GMarkupParseContext *context,
88 GError *error,
89 gpointer user_data)
90 {
91 g_string_append_printf (string, "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
test_in_chunks(const gchar * contents,gint length,gint chunk_size,GMarkupParseFlags flags)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 (&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 contents + i,
127 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
test_file(const gchar * filename,GMarkupParseFlags flags)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, &length_bytes, &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 (length_bytes);
172 if (contents_unterminated != NULL)
173 memcpy (contents_unterminated, contents, length_bytes);
174
175 /* Test with nul termination. */
176 context = g_markup_parse_context_new (&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, &col);
179 g_assert_cmpint (line, ==, 1);
180 g_assert_cmpint (col, ==, 1);
181
182 if (!g_markup_parse_context_parse (context, contents, -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 ("");
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_bytes, 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_unterminated, length_bytes, 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_bytes, chunk_sizes_bytes[i], flags) != 0)
210 n_failures++;
211 n_tests++;
212 }
213
214 g_free (contents);
215 g_free (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 *
get_expected_filename(const gchar * filename,GMarkupParseFlags flags)231 get_expected_filename (const gchar *filename,
232 GMarkupParseFlags flags)
233 {
234 gchar *f, *p, *expected;
235
236 f = g_strdup (filename);
237 p = strstr (f, ".gmarkup");
238 if (p)
239 *p = 0;
240 if (flags == 0)
241 expected = g_strconcat (f, ".expected", NULL);
242 else if (flags == G_MARKUP_TREAT_CDATA_AS_TEXT)
243 expected = g_strconcat (f, ".cdata-as-text", NULL);
244 else
245 g_assert_not_reached ();
246
247 g_free (f);
248
249 return expected;
250 }
251
252 static void
test_parse(gconstpointer d)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 (filename, "valid") != NULL;
263 expected_file = get_expected_filename (filename, 0);
264
265 depth = 0;
266 string = g_string_sized_new (0);
267
268 res = test_file (filename, 0);
269 g_assert_cmpint (res, ==, valid_input);
270
271 g_file_get_contents (expected_file, &expected, NULL, &error);
272 g_assert_no_error (error);
273 g_assert_cmpstr (string->str, ==, expected);
274 g_free (expected);
275
276 g_string_free (string, TRUE);
277
278 g_free (expected_file);
279
280 expected_file = get_expected_filename (filename, G_MARKUP_TREAT_CDATA_AS_TEXT);
281 if (g_file_test (expected_file, G_FILE_TEST_EXISTS))
282 {
283 depth = 0;
284 string = g_string_sized_new (0);
285
286 res = test_file (filename, G_MARKUP_TREAT_CDATA_AS_TEXT);
287 g_assert_cmpint (res, ==, valid_input);
288
289 g_file_get_contents (expected_file, &expected, NULL, &error);
290 g_assert_no_error (error);
291 g_assert_cmpstr (string->str, ==, expected);
292 g_free (expected);
293
294 g_string_free (string, TRUE);
295 }
296
297 g_free (expected_file);
298 }
299
300 int
main(int argc,char * argv[])301 main (int argc, char *argv[])
302 {
303 GDir *dir;
304 GError *error;
305 const gchar *name;
306 gchar *path;
307
308 g_setenv ("LC_ALL", "C", TRUE);
309 setlocale (LC_ALL, "");
310
311 g_test_init (&argc, &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 (argv[1], "--cdata-as-text") == 0)
320 {
321 flags = G_MARKUP_TREAT_CDATA_AS_TEXT;
322 arg = 2;
323 }
324 string = g_string_sized_new (0);
325 test_file (argv[arg], flags);
326 g_print ("%s", string->str);
327 return 0;
328 }
329
330 error = NULL;
331 path = g_test_build_filename (G_TEST_DIST, "markups", NULL);
332 dir = g_dir_open (path, 0, &error);
333 g_free (path);
334 g_assert_no_error (error);
335 while ((name = g_dir_read_name (dir)) != NULL)
336 {
337 if (!strstr (name, "gmarkup"))
338 continue;
339
340 path = g_strdup_printf ("/markup/parse/%s", name);
341 g_test_add_data_func_full (path, g_test_build_filename (G_TEST_DIST, "markups", name, NULL),
342 test_parse, g_free);
343 g_free (path);
344 }
345 g_dir_close (dir);
346
347 return g_test_run ();
348 }
349