1 #include <gtk/gtk.h>
2 #include <locale.h>
3 
4 #include <glib/gstdio.h>
5 
6 #include "../gtk/gtkcomposetable.h"
7 #include "../gtk/gtkimcontextsimpleseqs.h"
8 #include "testsuite/testutils.h"
9 
10 static void
append_escaped(GString * str,const char * s)11 append_escaped (GString    *str,
12                 const char *s)
13 {
14   for (const char *p = s; *p; p = g_utf8_next_char (p))
15     {
16       gunichar ch = g_utf8_get_char (p);
17       if (ch == '"')
18         g_string_append (str, "\\\"");
19       else if (ch == '\\')
20         g_string_append (str, "\\\\");
21       else if (g_unichar_isprint (ch))
22         g_string_append_unichar (str, ch);
23       else
24         {
25           guint n[8] = { 0, };
26           int i = 0;
27           while (ch != 0)
28             {
29               n[i++] = ch & 7;
30               ch = ch >> 3;
31             }
32           for (; i >= 0; i--)
33             g_string_append_printf (str, "\\%o", n[i]);
34         }
35     }
36 }
37 
38 static void
print_sequence(gunichar * sequence,int len,const char * value,gpointer data)39 print_sequence (gunichar   *sequence,
40                 int         len,
41                 const char *value,
42                 gpointer    data)
43 {
44   GString *str = data;
45 
46   for (int j = 0; j < len; j++)
47     g_string_append_printf (str, "<U%x> ", sequence[j]);
48 
49   g_string_append (str, ": \"");
50   append_escaped (str, value);
51   g_string_append (str, "\"");
52 
53   if (g_utf8_strlen (value, -1) == 1)
54     {
55       gunichar ch = g_utf8_get_char (value);
56       g_string_append_printf (str, " # U%x", ch);
57     }
58 
59   g_string_append_c (str, '\n');
60 }
61 
62 static char *
gtk_compose_table_print(GtkComposeTable * table)63 gtk_compose_table_print (GtkComposeTable *table)
64 {
65   GString *str;
66 
67   str = g_string_new ("");
68 
69   g_string_append_printf (str, "# n_sequences: %d\n# max_seq_len: %d\n# n_index_size: %d\n# data_size: %d\n# n_chars: %d\n",
70                           table->n_sequences,
71                           table->max_seq_len,
72                           table->n_index_size,
73                           table->data_size,
74                           table->n_chars);
75 
76   gtk_compose_table_foreach (table, print_sequence, str);
77 
78   return g_string_free (str, FALSE);
79 }
80 
81 static void
generate_output(const char * file)82 generate_output (const char *file)
83 {
84   GtkComposeTable *table;
85   char *output;
86 
87   table = gtk_compose_table_parse (file, NULL);
88   output = gtk_compose_table_print (table);
89 
90   g_print ("%s", output);
91 }
92 
93 static void
compose_table_compare(gconstpointer data)94 compose_table_compare (gconstpointer data)
95 {
96   const char *basename = data;
97   GtkComposeTable *table;
98   char *file;
99   char *expected;
100   char *output;
101   char *diff;
102   GError *error = NULL;
103 
104   file = g_test_build_filename (G_TEST_DIST, "compose", basename, NULL);
105   expected = g_strconcat (file, ".expected", NULL);
106 
107   table = gtk_compose_table_parse (file, NULL);
108   output = gtk_compose_table_print (table);
109 
110   diff = diff_with_file (expected, output, -1, &error);
111   g_assert_no_error (error);
112 
113   if (diff && diff[0])
114     {
115       g_print ("Resulting output doesn't match reference:\n%s", diff);
116       g_test_fail ();
117     }
118 
119   g_free (output);
120   g_free (file);
121   g_free (expected);
122 }
123 
124 static void
compose_table_cycle(void)125 compose_table_cycle (void)
126 {
127   if (g_test_subprocess ())
128     {
129       char *file;
130       GtkComposeTable *table;
131 
132       file = g_test_build_filename (G_TEST_DIST, "compose", "cycle", NULL);
133 
134       table = gtk_compose_table_parse (file, NULL);
135       g_assert_nonnull (table);
136       g_free (file);
137 
138       return;
139     }
140 
141   g_test_trap_subprocess (NULL, 0, 0);
142   g_test_trap_assert_stderr ("*include cycle detected*");
143   g_test_trap_assert_failed ();
144 }
145 
146 static void
compose_table_nofile(void)147 compose_table_nofile (void)
148 {
149   if (g_test_subprocess ())
150     {
151       char *file;
152       GtkComposeTable *table;
153 
154       file = g_build_filename (g_test_get_dir (G_TEST_DIST), "compose", "nofile", NULL);
155 
156       table = gtk_compose_table_parse (file, NULL);
157       g_assert_nonnull (table);
158       g_free (file);
159 
160       return;
161     }
162 
163   g_test_trap_subprocess (NULL, 0, 0);
164   g_test_trap_assert_stderr ("*No such file or directory*");
165   g_test_trap_assert_failed ();
166 }
167 
168 /* Check matching against a small table */
169 static void
compose_table_match(void)170 compose_table_match (void)
171 {
172   GtkComposeTable *table;
173   char *file;
174   guint buffer[8] = { 0, };
175   gboolean finish, match, ret;
176   GString *output;
177 
178   output = g_string_new ("");
179 
180   file = g_build_filename (g_test_get_dir (G_TEST_DIST), "compose", "match", NULL);
181 
182   table = gtk_compose_table_parse (file, NULL);
183 
184   buffer[0] = GDK_KEY_Multi_key;
185   buffer[1] = 0;
186   ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, output);
187   g_assert_true (ret);
188   g_assert_false (finish);
189   g_assert_false (match);
190   g_assert_true (output->len == 0);
191 
192   buffer[0] = GDK_KEY_a;
193   buffer[1] = 0;
194   ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, output);
195   g_assert_false (ret);
196   g_assert_false (finish);
197   g_assert_false (match);
198   g_assert_true (output->len == 0);
199 
200   buffer[0] = GDK_KEY_Multi_key;
201   buffer[1] = GDK_KEY_s;
202   buffer[2] = GDK_KEY_e;
203   ret = gtk_compose_table_check (table, buffer, 3, &finish, &match, output);
204   g_assert_true (ret);
205   g_assert_false (finish);
206   g_assert_false (match);
207   g_assert_true (output->len == 0);
208 
209   buffer[0] = GDK_KEY_Multi_key;
210   buffer[1] = GDK_KEY_s;
211   buffer[2] = GDK_KEY_e;
212   buffer[3] = GDK_KEY_q;
213   ret = gtk_compose_table_check (table, buffer, 4, &finish, &match, output);
214   g_assert_true (ret);
215   g_assert_false (finish);
216   g_assert_true (match);
217   g_assert_cmpstr (output->str, ==, "!");
218 
219   g_string_set_size (output, 0);
220 
221   buffer[0] = GDK_KEY_Multi_key;
222   buffer[1] = GDK_KEY_s;
223   buffer[2] = GDK_KEY_e;
224   buffer[3] = GDK_KEY_q;
225   buffer[4] = GDK_KEY_u;
226   ret = gtk_compose_table_check (table, buffer, 5, &finish, &match, output);
227   g_assert_true (ret);
228   g_assert_true (finish);
229   g_assert_true (match);
230   g_assert_cmpstr (output->str, ==, "?");
231 
232   g_string_set_size (output, 0);
233 
234   buffer[0] = GDK_KEY_Multi_key;
235   buffer[1] = GDK_KEY_l;
236   buffer[2] = GDK_KEY_o;
237   buffer[3] = GDK_KEY_n;
238   buffer[4] = GDK_KEY_g;
239   ret = gtk_compose_table_check (table, buffer, 5, &finish, &match, output);
240   g_assert_true (ret);
241   g_assert_true (finish);
242   g_assert_true (match);
243   g_assert_cmpstr (output->str, ==, "this is a long replacement string");
244 
245   g_string_free (output, TRUE);
246   g_free (file);
247 }
248 
249 extern const GtkComposeTable builtin_compose_table;
250 
251 /* just check some random sequences */
252 static void
compose_table_match_builtin(void)253 compose_table_match_builtin (void)
254 {
255   const GtkComposeTable *table = &builtin_compose_table;
256   guint buffer[8] = { 0, };
257   gboolean finish, match, ret;
258   GString *s;
259 
260   buffer[0] = GDK_KEY_Multi_key;
261   buffer[1] = 0;
262 
263   s = g_string_new ("");
264 
265   ret = gtk_compose_table_check (table, buffer, 1, &finish, &match, s);
266   g_assert_true (ret);
267   g_assert_false (finish);
268   g_assert_false (match);
269   g_assert_true (s->len == 0);
270 
271   buffer[0] = GDK_KEY_a;
272   buffer[1] = GDK_KEY_b;
273   buffer[2] = GDK_KEY_c;
274   buffer[3] = 0;
275 
276   ret = gtk_compose_table_check (table, buffer, 3, &finish, &match, s);
277   g_assert_false (ret);
278   g_assert_false (finish);
279   g_assert_false (match);
280   g_assert_true (s->len == 0);
281 
282   buffer[0] = GDK_KEY_Multi_key;
283   buffer[1] = GDK_KEY_parenleft;
284   buffer[2] = GDK_KEY_j;
285   buffer[3] = GDK_KEY_parenright;
286   buffer[4] = 0;
287 
288   ret = gtk_compose_table_check (table, buffer, 4, &finish, &match, s);
289   g_assert_true (ret);
290   g_assert_true (finish);
291   g_assert_true (match);
292   g_assert_cmpstr (s->str, ==, "ⓙ"); /* CIRCLED LATIN SMALL LETTER J */
293 
294   buffer[0] = GDK_KEY_dead_acute;
295   buffer[1] = GDK_KEY_space;
296   buffer[2] = 0;
297 
298   g_string_set_size (s, 0);
299 
300   ret = gtk_compose_table_check (table, buffer, 2, &finish, &match, s);
301   g_assert_true (ret);
302   g_assert_true (finish);
303   g_assert_true (match);
304   g_assert_cmpstr (s->str, ==, "'");
305 
306   buffer[0] = GDK_KEY_dead_acute;
307   buffer[1] = GDK_KEY_dead_acute;
308   buffer[2] = 0;
309 
310   g_string_set_size (s, 0);
311 
312   ret = gtk_compose_table_check (table, buffer, 2, &finish, &match, s);
313   g_assert_true (ret);
314   g_assert_true (finish);
315   g_assert_true (match);
316   g_assert_cmpstr (s->str, ==, "´");
317 
318   g_string_free (s, TRUE);
319 }
320 
321 static void
match_algorithmic(void)322 match_algorithmic (void)
323 {
324   guint buffer[8] = { 0, };
325   gboolean ret;
326   GString *output;
327 
328   output = g_string_new ("");
329 
330   buffer[0] = GDK_KEY_a;
331   buffer[1] = GDK_KEY_b;
332 
333   ret = gtk_check_algorithmically (buffer, 2, output);
334   g_assert_false (ret);
335   g_assert_cmpstr (output->str, ==, "");
336 
337   buffer[0] = GDK_KEY_dead_abovering;
338   buffer[1] = GDK_KEY_A;
339 
340   ret = gtk_check_algorithmically (buffer, 2, output);
341   g_assert_true (ret);
342   g_assert_cmpstr (output->str, ==, "Å");
343 
344   buffer[0] = GDK_KEY_A;
345   buffer[1] = GDK_KEY_dead_abovering;
346 
347   ret = gtk_check_algorithmically (buffer, 2, output);
348   g_assert_false (ret);
349   g_assert_cmpstr (output->str, ==, "");
350 
351   buffer[0] = GDK_KEY_dead_dasia;
352   buffer[1] = GDK_KEY_dead_perispomeni;
353   buffer[2] = GDK_KEY_Greek_alpha;
354 
355   ret = gtk_check_algorithmically (buffer, 3, output);
356   g_assert_true (ret);
357   g_assert_cmpstr (output->str, ==, "ᾶ\xcc\x94");
358 
359   buffer[0] = GDK_KEY_dead_perispomeni;
360   buffer[1] = GDK_KEY_dead_dasia;
361   buffer[2] = GDK_KEY_Greek_alpha;
362 
363   ret = gtk_check_algorithmically (buffer, 3, output);
364   g_assert_true (ret);
365   g_assert_cmpstr (output->str, ==, "ἇ");
366 
367   buffer[0] = GDK_KEY_dead_acute;
368   buffer[1] = GDK_KEY_dead_cedilla;
369   buffer[2] = GDK_KEY_c;
370 
371   ret = gtk_check_algorithmically (buffer, 2, output);
372   g_assert_true (ret);
373   g_assert_cmpstr (output->str, ==, "");
374 
375   ret = gtk_check_algorithmically (buffer, 3, output);
376   g_assert_true (ret);
377   g_assert_cmpstr (output->str, ==, "ḉ");
378 
379   buffer[0] = GDK_KEY_dead_cedilla;
380   buffer[1] = GDK_KEY_dead_acute;
381   buffer[2] = GDK_KEY_c;
382 
383   ret = gtk_check_algorithmically (buffer, 3, output);
384   g_assert_true (ret);
385   g_assert_cmpstr (output->str, ==, "ḉ");
386 
387   ret = gtk_check_algorithmically (buffer, 2, output);
388   g_assert_true (ret);
389 
390   buffer[0] = GDK_KEY_dead_acute;
391   buffer[1] = GDK_KEY_dead_cedilla;
392   buffer[2] = GDK_KEY_dead_grave;
393 
394   ret = gtk_check_algorithmically (buffer, 3, output);
395   g_assert_true (ret);
396   g_assert_cmpstr (output->str, ==, "");
397 
398   buffer[0] = GDK_KEY_dead_diaeresis;
399   buffer[1] = GDK_KEY_a;
400 
401   ret = gtk_check_algorithmically (buffer, 2, output);
402   g_assert_true (ret);
403   g_assert_cmpstr (output->str, ==, "ä");
404 
405   g_string_free (output, TRUE);
406 }
407 
408 int
main(int argc,char * argv[])409 main (int argc, char *argv[])
410 {
411   if (argc == 3 && strcmp (argv[1], "--generate") == 0)
412     {
413       gtk_disable_setlocale();
414       setlocale (LC_ALL, "en_US.UTF-8");
415 
416       gtk_init ();
417 
418       /* Ensure that the builtin table is initialized */
419       GtkIMContext *ctx = gtk_im_context_simple_new ();
420       g_object_unref (ctx);
421 
422       generate_output (argv[2]);
423       return 0;
424     }
425 
426   gtk_test_init (&argc, &argv, NULL);
427 
428   /* Ensure that the builtin table is initialized */
429   GtkIMContext *ctx = gtk_im_context_simple_new ();
430   g_object_unref (ctx);
431 
432   g_test_add_data_func ("/compose-table/basic", "basic", compose_table_compare);
433   g_test_add_data_func ("/compose-table/long", "long", compose_table_compare);
434   g_test_add_data_func ("/compose-table/octal", "octal", compose_table_compare);
435   g_test_add_data_func ("/compose-table/hex", "hex", compose_table_compare);
436   g_test_add_data_func ("/compose-table/codepoint", "codepoint", compose_table_compare);
437   g_test_add_data_func ("/compose-table/multi", "multi", compose_table_compare);
438   g_test_add_data_func ("/compose-table/strings", "strings", compose_table_compare);
439   g_test_add_data_func ("/compose-table/include", "include", compose_table_compare);
440   g_test_add_data_func ("/compose-table/system", "system", compose_table_compare);
441   g_test_add_func ("/compose-table/include-cycle", compose_table_cycle);
442   g_test_add_func ("/compose-table/include-nofile", compose_table_nofile);
443   g_test_add_func ("/compose-table/match", compose_table_match);
444   g_test_add_func ("/compose-table/match-builtin", compose_table_match_builtin);
445   g_test_add_func ("/compose-table/match-algorithmic", match_algorithmic);
446 
447   return g_test_run ();
448 }
449