compose.c 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212
  1. /*
  2. * Copyright © 2014 Ran Benita <ran234@gmail.com>
  3. * SPDX-License-Identifier: MIT
  4. */
  5. #include "config.h"
  6. #include "test-config.h"
  7. #include <stdint.h>
  8. #include <stdlib.h>
  9. #include <time.h>
  10. #include <errno.h>
  11. #include <locale.h>
  12. #include <stdio.h>
  13. #include "xkbcommon/xkbcommon-compose.h"
  14. #include "xkbcommon/xkbcommon-keysyms.h"
  15. #include "test.h"
  16. #include "src/utf8.h"
  17. #include "src/keysym.h"
  18. #include "src/compose/constants.h"
  19. #include "src/compose/parser.h"
  20. #include "src/compose/escape.h"
  21. #include "src/compose/dump.h"
  22. #include "src/utils-random.h"
  23. #include "test/compose-iter.h"
  24. #include "test/utils-text.h"
  25. static const char *
  26. compose_status_string(enum xkb_compose_status status)
  27. {
  28. switch (status) {
  29. case XKB_COMPOSE_NOTHING:
  30. return "nothing";
  31. case XKB_COMPOSE_COMPOSING:
  32. return "composing";
  33. case XKB_COMPOSE_COMPOSED:
  34. return "composed";
  35. case XKB_COMPOSE_CANCELLED:
  36. return "cancelled";
  37. }
  38. return "<invalid-status>";
  39. }
  40. static const char *
  41. feed_result_string(enum xkb_compose_feed_result result)
  42. {
  43. switch (result) {
  44. case XKB_COMPOSE_FEED_IGNORED:
  45. return "ignored";
  46. case XKB_COMPOSE_FEED_ACCEPTED:
  47. return "accepted";
  48. }
  49. return "<invalid-result>";
  50. }
  51. /*
  52. * Feed a sequence of keysyms to a fresh compose state and test the outcome.
  53. *
  54. * The varargs consists of lines in the following format:
  55. * <input keysym> <expected feed result> <expected status> <expected string> <expected keysym>
  56. * Terminated by a line consisting only of XKB_KEY_NoSymbol.
  57. */
  58. static bool
  59. test_compose_seq_va(struct xkb_compose_table *table, va_list ap)
  60. {
  61. int ret;
  62. struct xkb_compose_state *state;
  63. char buffer[MAX(XKB_COMPOSE_MAX_STRING_SIZE, XKB_KEYSYM_NAME_MAX_SIZE)];
  64. state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
  65. assert(state);
  66. for (int i = 1; ; i++) {
  67. xkb_keysym_t input_keysym;
  68. enum xkb_compose_feed_result result, expected_result;
  69. enum xkb_compose_status status, expected_status;
  70. const char *expected_string;
  71. xkb_keysym_t keysym, expected_keysym;
  72. input_keysym = va_arg(ap, xkb_keysym_t);
  73. if (input_keysym == XKB_KEY_NoSymbol)
  74. break;
  75. expected_result = va_arg(ap, enum xkb_compose_feed_result);
  76. expected_status = va_arg(ap, enum xkb_compose_status);
  77. expected_string = va_arg(ap, const char *);
  78. expected_keysym = va_arg(ap, xkb_keysym_t);
  79. result = xkb_compose_state_feed(state, input_keysym);
  80. if (result != expected_result) {
  81. fprintf(stderr, "after feeding %d keysyms:\n", i);
  82. fprintf(stderr, "expected feed result: %s\n",
  83. feed_result_string(expected_result));
  84. fprintf(stderr, "got feed result: %s\n",
  85. feed_result_string(result));
  86. goto fail;
  87. }
  88. status = xkb_compose_state_get_status(state);
  89. if (status != expected_status) {
  90. fprintf(stderr, "after feeding %d keysyms:\n", i);
  91. fprintf(stderr, "expected status: %s\n",
  92. compose_status_string(expected_status));
  93. fprintf(stderr, "got status: %s\n",
  94. compose_status_string(status));
  95. goto fail;
  96. }
  97. ret = xkb_compose_state_get_utf8(state, buffer, sizeof(buffer));
  98. if (ret < 0 || (size_t) ret >= sizeof(buffer)) {
  99. fprintf(stderr, "after feeding %d keysyms:\n", i);
  100. fprintf(stderr, "expected string: %s\n", expected_string);
  101. fprintf(stderr, "got error: %d\n", ret);
  102. goto fail;
  103. }
  104. if (!streq(buffer, expected_string)) {
  105. fprintf(stderr, "after feeding %d keysyms:\n", i);
  106. fprintf(stderr, "expected string: %s\n", strempty(expected_string));
  107. fprintf(stderr, "got string: %s\n", buffer);
  108. goto fail;
  109. }
  110. keysym = xkb_compose_state_get_one_sym(state);
  111. if (keysym != expected_keysym) {
  112. fprintf(stderr, "after feeding %d keysyms:\n", i);
  113. xkb_keysym_get_name(expected_keysym, buffer, sizeof(buffer));
  114. fprintf(stderr, "expected keysym: %s\n", buffer);
  115. xkb_keysym_get_name(keysym, buffer, sizeof(buffer));
  116. fprintf(stderr, "got keysym (%#06"PRIx32"): %s\n", keysym, buffer);
  117. goto fail;
  118. }
  119. }
  120. xkb_compose_state_unref(state);
  121. return true;
  122. fail:
  123. xkb_compose_state_unref(state);
  124. return false;
  125. }
  126. static bool
  127. test_compose_seq(struct xkb_compose_table *table, ...)
  128. {
  129. va_list ap;
  130. bool ok;
  131. va_start(ap, table);
  132. ok = test_compose_seq_va(table, ap);
  133. va_end(ap);
  134. return ok;
  135. }
  136. static bool
  137. test_compose_seq_buffer(struct xkb_context *ctx, const char *buffer, ...)
  138. {
  139. va_list ap;
  140. bool ok;
  141. struct xkb_compose_table *table;
  142. table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "",
  143. XKB_COMPOSE_FORMAT_TEXT_V1,
  144. XKB_COMPOSE_COMPILE_NO_FLAGS);
  145. assert(table);
  146. va_start(ap, buffer);
  147. ok = test_compose_seq_va(table, ap);
  148. va_end(ap);
  149. xkb_compose_table_unref(table);
  150. return ok;
  151. }
  152. static void
  153. test_compose_utf8_bom(struct xkb_context *ctx)
  154. {
  155. const char buffer[] = "\xef\xbb\xbf<A> : X";
  156. /* Reject invalid flags */
  157. assert(!xkb_compose_table_new_from_buffer(ctx,
  158. buffer, sizeof(buffer), "",
  159. XKB_COMPOSE_FORMAT_TEXT_V1,
  160. -1));
  161. assert(!xkb_compose_table_new_from_buffer(ctx,
  162. buffer, sizeof(buffer), "",
  163. XKB_COMPOSE_FORMAT_TEXT_V1,
  164. 0xffff));
  165. assert(test_compose_seq_buffer(ctx, buffer,
  166. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "X", XKB_KEY_X,
  167. XKB_KEY_NoSymbol));
  168. }
  169. static void
  170. test_invalid_encodings(struct xkb_context *ctx)
  171. {
  172. struct xkb_compose_table *table;
  173. /* ISO 8859-1 (latin1) */
  174. const char iso_8859_1[] = "<A> : \"\xe1\" acute";
  175. assert(!test_compose_seq_buffer(ctx, iso_8859_1,
  176. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "\xc3\xa1", XKB_KEY_acute,
  177. XKB_KEY_NoSymbol));
  178. /* UTF-16LE */
  179. const char utf_16_le[] =
  180. "<\0A\0>\0 \0:\0 \0X\0\n\0"
  181. "<\0B\0>\0 \0:\0 \0Y\0";
  182. table = xkb_compose_table_new_from_buffer(ctx,
  183. utf_16_le, sizeof(utf_16_le), "",
  184. XKB_COMPOSE_FORMAT_TEXT_V1,
  185. XKB_COMPOSE_COMPILE_NO_FLAGS);
  186. assert(!table);
  187. /* UTF-16BE */
  188. const char utf_16_be[] =
  189. "\0<\0A\0>\0 \0:\0 \0X\0\n"
  190. "\0<\0B\0>\0 \0:\0 \0Y";
  191. table = xkb_compose_table_new_from_buffer(ctx,
  192. utf_16_be, sizeof(utf_16_be), "",
  193. XKB_COMPOSE_FORMAT_TEXT_V1,
  194. XKB_COMPOSE_COMPILE_NO_FLAGS);
  195. assert(!table);
  196. /* UTF-16BE with BOM */
  197. const char utf_16_be_bom[] =
  198. "\xfe\xff"
  199. "\0<\0A\0>\0 \0:\0 \0X\0\n"
  200. "\0<\0B\0>\0 \0:\0 \0Y";
  201. table = xkb_compose_table_new_from_buffer(ctx,
  202. utf_16_be_bom, sizeof(utf_16_be_bom), "",
  203. XKB_COMPOSE_FORMAT_TEXT_V1,
  204. XKB_COMPOSE_COMPILE_NO_FLAGS);
  205. assert(!table);
  206. /* UTF-32LE */
  207. const char utf_32_le[] =
  208. "<\0\0\0A\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0X\0\0\0\n\0\0\0"
  209. "<\0\0\0B\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0Y\0\0\0";
  210. table = xkb_compose_table_new_from_buffer(ctx,
  211. utf_32_le, sizeof(utf_32_le), "",
  212. XKB_COMPOSE_FORMAT_TEXT_V1,
  213. XKB_COMPOSE_COMPILE_NO_FLAGS);
  214. assert(!table);
  215. /* UTF-32LE with BOM */
  216. const char utf_32_le_bom[] =
  217. "\xff\xfe\0\0"
  218. "<\0\0\0A\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0X\0\0\0\n\0\0\0"
  219. "<\0\0\0B\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0Y\0\0\0";
  220. table = xkb_compose_table_new_from_buffer(ctx,
  221. utf_32_le_bom, sizeof(utf_32_le_bom), "",
  222. XKB_COMPOSE_FORMAT_TEXT_V1,
  223. XKB_COMPOSE_COMPILE_NO_FLAGS);
  224. assert(!table);
  225. /* UTF-32BE */
  226. const char utf_32_be[] =
  227. "\0\0\0<\0\0\0A\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0X\0\0\0\n\0\0\0"
  228. "<\0\0\0B\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0Y";
  229. table = xkb_compose_table_new_from_buffer(ctx,
  230. utf_32_be, sizeof(utf_32_be), "",
  231. XKB_COMPOSE_FORMAT_TEXT_V1,
  232. XKB_COMPOSE_COMPILE_NO_FLAGS);
  233. assert(!table);
  234. }
  235. static void
  236. test_seqs(struct xkb_context *ctx)
  237. {
  238. struct xkb_compose_table *table;
  239. char *path;
  240. FILE *file;
  241. path = test_get_path("locale/en_US.UTF-8/Compose");
  242. file = fopen(path, "rb");
  243. assert(file);
  244. free(path);
  245. table = xkb_compose_table_new_from_file(ctx, file, "",
  246. XKB_COMPOSE_FORMAT_TEXT_V1,
  247. XKB_COMPOSE_COMPILE_NO_FLAGS);
  248. assert(table);
  249. fclose(file);
  250. assert(test_compose_seq(table,
  251. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  252. XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde,
  253. XKB_KEY_NoSymbol));
  254. assert(test_compose_seq(table,
  255. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  256. XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde,
  257. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  258. XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde,
  259. XKB_KEY_NoSymbol));
  260. assert(test_compose_seq(table,
  261. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  262. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde,
  263. XKB_KEY_NoSymbol));
  264. assert(test_compose_seq(table,
  265. XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  266. XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "'", XKB_KEY_apostrophe,
  267. XKB_KEY_Caps_Lock, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSED, "'", XKB_KEY_apostrophe,
  268. XKB_KEY_NoSymbol));
  269. assert(test_compose_seq(table,
  270. XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  271. XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "´", XKB_KEY_acute,
  272. XKB_KEY_NoSymbol));
  273. assert(test_compose_seq(table,
  274. XKB_KEY_Multi_key, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  275. XKB_KEY_Shift_L, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  276. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  277. XKB_KEY_Caps_Lock, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  278. XKB_KEY_Control_L, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  279. XKB_KEY_T, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "@", XKB_KEY_at,
  280. XKB_KEY_NoSymbol));
  281. assert(test_compose_seq(table,
  282. XKB_KEY_7, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  283. XKB_KEY_a, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  284. XKB_KEY_b, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  285. XKB_KEY_NoSymbol));
  286. assert(test_compose_seq(table,
  287. XKB_KEY_Multi_key, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  288. XKB_KEY_apostrophe, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  289. XKB_KEY_7, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol,
  290. XKB_KEY_7, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  291. XKB_KEY_Caps_Lock, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  292. XKB_KEY_NoSymbol));
  293. xkb_compose_table_unref(table);
  294. /* Make sure one-keysym sequences work. */
  295. assert(test_compose_seq_buffer(ctx,
  296. "<A> : \"foo\" X \n"
  297. "<B> <A> : \"baz\" Y \n",
  298. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X,
  299. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X,
  300. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  301. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  302. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Y,
  303. XKB_KEY_NoSymbol));
  304. /* No sequences at all. */
  305. assert(test_compose_seq_buffer(ctx,
  306. "",
  307. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  308. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  309. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  310. XKB_KEY_Multi_key, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  311. XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  312. XKB_KEY_NoSymbol));
  313. /* Only keysym - string derived from keysym. */
  314. assert(test_compose_seq_buffer(ctx,
  315. "<A> <B> : X \n"
  316. "<B> <A> : dollar \n"
  317. "<C> : dead_acute \n",
  318. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  319. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "X", XKB_KEY_X,
  320. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  321. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "$", XKB_KEY_dollar,
  322. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "", XKB_KEY_dead_acute,
  323. XKB_KEY_NoSymbol));
  324. /* Make sure a cancelling keysym doesn't start a new sequence. */
  325. assert(test_compose_seq_buffer(ctx,
  326. "<A> <B> : X \n"
  327. "<C> <D> : Y \n",
  328. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  329. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol,
  330. XKB_KEY_D, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  331. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  332. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol,
  333. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  334. XKB_KEY_D, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "Y", XKB_KEY_Y,
  335. XKB_KEY_NoSymbol));
  336. }
  337. static void
  338. test_conflicting(struct xkb_context *ctx)
  339. {
  340. // new is prefix of old
  341. assert(test_compose_seq_buffer(ctx,
  342. "<A> <B> <C> : \"foo\" A \n"
  343. "<A> <B> : \"bar\" B \n",
  344. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  345. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B,
  346. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  347. XKB_KEY_NoSymbol));
  348. // old is a prefix of new
  349. assert(test_compose_seq_buffer(ctx,
  350. "<A> <B> : \"bar\" B \n"
  351. "<A> <B> <C> : \"foo\" A \n",
  352. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  353. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  354. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A,
  355. XKB_KEY_NoSymbol));
  356. // new duplicate of old
  357. assert(test_compose_seq_buffer(ctx,
  358. "<A> <B> : \"bar\" B \n"
  359. "<A> <B> : \"bar\" B \n",
  360. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  361. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B,
  362. XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol,
  363. XKB_KEY_NoSymbol));
  364. // new same length as old #1
  365. assert(test_compose_seq_buffer(ctx,
  366. "<A> <B> : \"foo\" A \n"
  367. "<A> <B> : \"bar\" B \n",
  368. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  369. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B,
  370. XKB_KEY_NoSymbol));
  371. // new same length as old #2
  372. assert(test_compose_seq_buffer(ctx,
  373. "<A> <B> : \"foo\" A \n"
  374. "<A> <B> : \"foo\" B \n",
  375. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  376. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_B,
  377. XKB_KEY_NoSymbol));
  378. // new same length as old #3: overwritable string: do not allocate
  379. assert(test_compose_seq_buffer(ctx,
  380. "<A> <B> : \"foo\" A \n"
  381. "<A> <B> : \"qu\" A \n",
  382. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  383. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "qu", XKB_KEY_A,
  384. XKB_KEY_NoSymbol));
  385. // new same length as old #4: no-overwritable string: allocate
  386. assert(test_compose_seq_buffer(ctx,
  387. "<A> <B> : \"foo\" A \n"
  388. "<A> <B> : \"quux\" A \n",
  389. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  390. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "quux", XKB_KEY_A,
  391. XKB_KEY_NoSymbol));
  392. // new same length as old #5: ensure string is reset
  393. assert(test_compose_seq_buffer(ctx,
  394. "<A> <B> : \"foo\" A \n"
  395. "<A> <B> : B \n",
  396. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  397. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "B", XKB_KEY_B,
  398. XKB_KEY_NoSymbol));
  399. // new same length as old #6: ensure keysym is reset
  400. assert(test_compose_seq_buffer(ctx,
  401. "<A> <B> : \"foo\" A \n"
  402. "<A> <B> : \"bar\" \n",
  403. XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  404. XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_NoSymbol,
  405. XKB_KEY_NoSymbol));
  406. }
  407. static void
  408. test_state(struct xkb_context *ctx)
  409. {
  410. struct xkb_compose_table *table;
  411. struct xkb_compose_state *state;
  412. char *path;
  413. FILE *file;
  414. path = test_get_path("locale/en_US.UTF-8/Compose");
  415. file = fopen(path, "rb");
  416. assert(file);
  417. free(path);
  418. /* Reject invalid flags */
  419. assert(!xkb_compose_table_new_from_file(ctx, file, "",
  420. XKB_COMPOSE_FORMAT_TEXT_V1, -1));
  421. assert(!xkb_compose_table_new_from_file(ctx, file, "",
  422. XKB_COMPOSE_FORMAT_TEXT_V1, 0xffff));
  423. table = xkb_compose_table_new_from_file(ctx, file, "",
  424. XKB_COMPOSE_FORMAT_TEXT_V1,
  425. XKB_COMPOSE_COMPILE_NO_FLAGS);
  426. assert(table);
  427. fclose(file);
  428. /* Reject unsupported flags */
  429. assert(!xkb_compose_state_new(table, -1));
  430. assert(!xkb_compose_state_new(table, 0xffff));
  431. state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
  432. assert(state);
  433. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  434. xkb_compose_state_reset(state);
  435. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  436. xkb_compose_state_feed(state, XKB_KEY_NoSymbol);
  437. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  438. xkb_compose_state_feed(state, XKB_KEY_Multi_key);
  439. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
  440. xkb_compose_state_reset(state);
  441. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  442. xkb_compose_state_feed(state, XKB_KEY_Multi_key);
  443. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
  444. xkb_compose_state_feed(state, XKB_KEY_Multi_key);
  445. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_CANCELLED);
  446. xkb_compose_state_feed(state, XKB_KEY_Multi_key);
  447. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
  448. xkb_compose_state_feed(state, XKB_KEY_Multi_key);
  449. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_CANCELLED);
  450. xkb_compose_state_reset(state);
  451. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  452. xkb_compose_state_feed(state, XKB_KEY_dead_acute);
  453. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
  454. xkb_compose_state_feed(state, XKB_KEY_A);
  455. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSED);
  456. xkb_compose_state_reset(state);
  457. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  458. xkb_compose_state_feed(state, XKB_KEY_dead_acute);
  459. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING);
  460. xkb_compose_state_feed(state, XKB_KEY_A);
  461. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSED);
  462. xkb_compose_state_reset(state);
  463. xkb_compose_state_feed(state, XKB_KEY_NoSymbol);
  464. assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING);
  465. xkb_compose_state_unref(state);
  466. xkb_compose_table_unref(table);
  467. }
  468. static void
  469. test_XCOMPOSEFILE(struct xkb_context *ctx)
  470. {
  471. struct xkb_compose_table *table;
  472. char *path;
  473. /* Error: directory */
  474. path = test_get_path("locale/en_US.UTF-8");
  475. setenv("XCOMPOSEFILE", path, 1);
  476. free(path);
  477. table = xkb_compose_table_new_from_locale(ctx, "blabla",
  478. XKB_COMPOSE_COMPILE_NO_FLAGS);
  479. assert_printf(errno != ENODEV && errno != EISDIR,
  480. "Should not be an error from `map_file`\n");
  481. assert(!table);
  482. /* OK: regular file */
  483. path = test_get_path("locale/en_US.UTF-8/Compose");
  484. setenv("XCOMPOSEFILE", path, 1);
  485. free(path);
  486. table = xkb_compose_table_new_from_locale(ctx, "blabla",
  487. XKB_COMPOSE_COMPILE_NO_FLAGS);
  488. assert(table);
  489. unsetenv("XCOMPOSEFILE");
  490. assert(test_compose_seq(table,
  491. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  492. XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde,
  493. XKB_KEY_NoSymbol));
  494. xkb_compose_table_unref(table);
  495. }
  496. static void
  497. test_from_locale(struct xkb_context *ctx)
  498. {
  499. struct xkb_compose_table *table;
  500. char *path;
  501. path = test_get_path("locale");
  502. setenv("XLOCALEDIR", path, 1);
  503. free(path);
  504. /* Reject invalid flags */
  505. assert(!xkb_compose_table_new_from_locale(ctx, "en_US.UTF-8", -1));
  506. assert(!xkb_compose_table_new_from_locale(ctx, "en_US.UTF-8", 0xffff));
  507. /* Direct directory name match. */
  508. table = xkb_compose_table_new_from_locale(ctx, "en_US.UTF-8",
  509. XKB_COMPOSE_COMPILE_NO_FLAGS);
  510. assert(table);
  511. xkb_compose_table_unref(table);
  512. /* Direct locale name match. */
  513. table = xkb_compose_table_new_from_locale(ctx, "C.UTF-8",
  514. XKB_COMPOSE_COMPILE_NO_FLAGS);
  515. assert(table);
  516. xkb_compose_table_unref(table);
  517. /* Alias. */
  518. table = xkb_compose_table_new_from_locale(ctx, "univ.utf8",
  519. XKB_COMPOSE_COMPILE_NO_FLAGS);
  520. assert(table);
  521. xkb_compose_table_unref(table);
  522. /* Special case - C. */
  523. table = xkb_compose_table_new_from_locale(ctx, "C",
  524. XKB_COMPOSE_COMPILE_NO_FLAGS);
  525. assert(table);
  526. xkb_compose_table_unref(table);
  527. #if HAVE_NEWLOCALE
  528. /* Test custom locale. Require installing it system-wide */
  529. /* NOTE: Keep the locale name in sync with the localedef call in our CI */
  530. static const char * const custom_locale = "xx_YY.UTF-8";
  531. locale_t loc = newlocale(LC_ALL, custom_locale, (locale_t) 0);
  532. table = xkb_compose_table_new_from_locale(ctx, custom_locale,
  533. XKB_COMPOSE_COMPILE_NO_FLAGS);
  534. if (loc == (locale_t) 0) {
  535. /* Locale is not installed: no fallback */
  536. assert(!table);
  537. } else {
  538. /* Locale is installed */
  539. freelocale(loc);
  540. assert(table);
  541. xkb_compose_table_unref(table);
  542. }
  543. #endif
  544. /* Bogus - not found. */
  545. table = xkb_compose_table_new_from_locale(ctx, "blabla",
  546. XKB_COMPOSE_COMPILE_NO_FLAGS);
  547. assert(!table);
  548. unsetenv("XLOCALEDIR");
  549. }
  550. static void
  551. test_modifier_syntax(struct xkb_context *ctx)
  552. {
  553. const char *table_string;
  554. /* We don't do anything with the modifiers, but make sure we can parse
  555. * them. */
  556. assert(test_compose_seq_buffer(ctx,
  557. "None <A> : X \n"
  558. "Shift <B> : Y \n"
  559. "Ctrl <C> : Y \n"
  560. "Alt <D> : Y \n"
  561. "Caps <E> : Y \n"
  562. "Lock <F> : Y \n"
  563. "Shift Ctrl <G> : Y \n"
  564. "~Shift <H> : Y \n"
  565. "~Shift Ctrl <I> : Y \n"
  566. "Shift ~Ctrl <J> : Y \n"
  567. "Shift ~Ctrl ~Alt <K> : Y \n"
  568. "! Shift <B> : Y \n"
  569. "! Ctrl <C> : Y \n"
  570. "! Alt <D> : Y \n"
  571. "! Caps <E> : Y \n"
  572. "! Lock <F> : Y \n"
  573. "! Shift Ctrl <G> : Y \n"
  574. "! ~Shift <H> : Y \n"
  575. "! ~Shift Ctrl <I> : Y \n"
  576. "! Shift ~Ctrl <J> : Y \n"
  577. "! Shift ~Ctrl ~Alt <K> : Y \n"
  578. "<L> ! Shift <M> : Y \n"
  579. "None <N> ! Shift <O> : Y \n"
  580. "None <P> ! Shift <Q> : Y \n",
  581. XKB_KEY_NoSymbol));
  582. fprintf(stderr, "<START bad input string>\n");
  583. table_string =
  584. "! None <A> : X \n"
  585. "! Foo <B> : X \n"
  586. "None ! Shift <C> : X \n"
  587. "! ! <D> : X \n"
  588. "! ~ <E> : X \n"
  589. "! ! <F> : X \n"
  590. "! Ctrl ! Ctrl <G> : X \n"
  591. "<H> ! : X \n"
  592. "<I> None : X \n"
  593. "None None <J> : X \n"
  594. "<K> : !Shift X \n";
  595. assert(!xkb_compose_table_new_from_buffer(ctx, table_string,
  596. strlen(table_string), "C",
  597. XKB_COMPOSE_FORMAT_TEXT_V1,
  598. XKB_COMPOSE_COMPILE_NO_FLAGS));
  599. fprintf(stderr, "<END bad input string>\n");
  600. }
  601. static void
  602. test_include(struct xkb_context *ctx)
  603. {
  604. char *path, *table_string;
  605. path = test_get_path("locale/en_US.UTF-8/Compose");
  606. assert(path);
  607. /* We don't have a mechanism to change the include paths like we
  608. * have for keymaps. So we must include the full path. */
  609. table_string = asprintf_safe("<dead_tilde> <space> : \"foo\" X\n"
  610. "include \"%s\"\n"
  611. "<dead_tilde> <dead_tilde> : \"bar\" Y\n", path);
  612. assert(table_string);
  613. assert(test_compose_seq_buffer(ctx, table_string,
  614. /* No conflict. */
  615. XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  616. XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "´", XKB_KEY_acute,
  617. /* Comes before - doesn't override. */
  618. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  619. XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde,
  620. /* Comes after - does override. */
  621. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  622. XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y,
  623. XKB_KEY_NoSymbol));
  624. free(path);
  625. free(table_string);
  626. }
  627. static void
  628. test_override(struct xkb_context *ctx)
  629. {
  630. const char *table_string = "<dead_circumflex> <dead_circumflex> : \"foo\" X\n"
  631. "<dead_circumflex> <e> : \"bar\" Y\n"
  632. "<dead_circumflex> <dead_circumflex> <e> : \"baz\" Z\n";
  633. assert(test_compose_seq_buffer(ctx, table_string,
  634. /* Comes after - does override. */
  635. XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  636. XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  637. XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Z,
  638. /* Override does not affect sibling nodes */
  639. XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  640. XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y,
  641. XKB_KEY_NoSymbol));
  642. }
  643. static bool
  644. test_eq_entry_va(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym_ref, const char *utf8_ref, va_list ap)
  645. {
  646. assert (entry != NULL);
  647. assert (xkb_compose_table_entry_keysym(entry) == keysym_ref);
  648. const char *utf8 = xkb_compose_table_entry_utf8(entry);
  649. assert (utf8 && utf8_ref && strcmp(utf8, utf8_ref) == 0);
  650. size_t nsyms;
  651. const xkb_keysym_t *sequence = xkb_compose_table_entry_sequence(entry, &nsyms);
  652. xkb_keysym_t keysym;
  653. for (unsigned int k = 0; ; k++) {
  654. keysym = va_arg(ap, xkb_keysym_t);
  655. if (keysym == XKB_KEY_NoSymbol) {
  656. return (k == nsyms - 1);
  657. }
  658. assert (k < nsyms);
  659. assert (keysym == sequence[k]);
  660. }
  661. }
  662. static bool
  663. test_eq_entry(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym, const char *utf8, ...)
  664. {
  665. va_list ap;
  666. bool ok;
  667. va_start(ap, utf8);
  668. ok = test_eq_entry_va(entry, keysym, utf8, ap);
  669. va_end(ap);
  670. return ok;
  671. }
  672. static bool
  673. test_eq_entries(struct xkb_compose_table_entry *entry1, struct xkb_compose_table_entry *entry2)
  674. {
  675. if (!entry1 || !entry2)
  676. goto error;
  677. bool ok = true;
  678. if (entry1->keysym != entry2->keysym ||
  679. !streq_null(entry1->utf8, entry2->utf8) ||
  680. entry1->sequence_length != entry2->sequence_length)
  681. ok = false;
  682. for (size_t k = 0; k < entry1->sequence_length; k++) {
  683. if (entry1->sequence[k] != entry2->sequence[k])
  684. ok = false;
  685. }
  686. if (ok)
  687. return true;
  688. error:
  689. #define print_entry(msg, entry) \
  690. fprintf(stderr, msg); \
  691. if (entry) \
  692. print_compose_table_entry(stderr, entry); \
  693. else \
  694. fprintf(stderr, "\n");
  695. print_entry("Expected: ", entry1);
  696. print_entry("Got: ", entry2);
  697. #undef print_entry
  698. return false;
  699. }
  700. static void
  701. compose_traverse_fn(struct xkb_compose_table_entry *entry_ref, void *data)
  702. {
  703. struct xkb_compose_table_iterator *iter = (struct xkb_compose_table_iterator *)data;
  704. struct xkb_compose_table_entry *entry = xkb_compose_table_iterator_next(iter);
  705. assert(test_eq_entries(entry_ref, entry));
  706. }
  707. static void
  708. test_traverse(struct xkb_context *ctx, size_t quickcheck_loops)
  709. {
  710. struct xkb_compose_table *table;
  711. struct xkb_compose_table_iterator *iter;
  712. /* Empty table */
  713. table = xkb_compose_table_new_from_buffer(ctx, "", 0, "",
  714. XKB_COMPOSE_FORMAT_TEXT_V1,
  715. XKB_COMPOSE_COMPILE_NO_FLAGS);
  716. assert(table);
  717. iter = xkb_compose_table_iterator_new(table);
  718. assert (xkb_compose_table_iterator_next(iter) == NULL);
  719. xkb_compose_table_iterator_free(iter);
  720. xkb_compose_table_unref(table);
  721. /* Non-empty table */
  722. const char *buffer = "<dead_circumflex> <dead_circumflex> : \"foo\" X\n"
  723. "<Ahook> <x> : \"foobar\"\n"
  724. "<Multi_key> <o> <e> : oe\n"
  725. "<dead_circumflex> <e> : \"bar\" Y\n"
  726. "<Multi_key> <a> <e> : \"æ\" ae\n"
  727. "<dead_circumflex> <a> : \"baz\" Z\n"
  728. "<dead_acute> <e> : \"é\" eacute\n"
  729. "<Multi_key> <a> <a> <c>: \"aac\"\n"
  730. "<Multi_key> <a> <a> <b>: \"aab\"\n"
  731. "<Multi_key> <a> <a> <a>: \"aaa\"\n";
  732. table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "",
  733. XKB_COMPOSE_FORMAT_TEXT_V1,
  734. XKB_COMPOSE_COMPILE_NO_FLAGS);
  735. assert(table);
  736. iter = xkb_compose_table_iterator_new(table);
  737. test_eq_entry(xkb_compose_table_iterator_next(iter),
  738. XKB_KEY_eacute, "é",
  739. XKB_KEY_dead_acute, XKB_KEY_e, XKB_KEY_NoSymbol);
  740. test_eq_entry(xkb_compose_table_iterator_next(iter),
  741. XKB_KEY_Z, "baz",
  742. XKB_KEY_dead_circumflex, XKB_KEY_a, XKB_KEY_NoSymbol);
  743. test_eq_entry(xkb_compose_table_iterator_next(iter),
  744. XKB_KEY_Y, "bar",
  745. XKB_KEY_dead_circumflex, XKB_KEY_e, XKB_KEY_NoSymbol);
  746. test_eq_entry(xkb_compose_table_iterator_next(iter),
  747. XKB_KEY_X, "foo",
  748. XKB_KEY_dead_circumflex, XKB_KEY_dead_circumflex, XKB_KEY_NoSymbol);
  749. test_eq_entry(xkb_compose_table_iterator_next(iter),
  750. XKB_KEY_NoSymbol, "aaa",
  751. XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_a, XKB_KEY_NoSymbol);
  752. test_eq_entry(xkb_compose_table_iterator_next(iter),
  753. XKB_KEY_NoSymbol, "aab",
  754. XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_b, XKB_KEY_NoSymbol);
  755. test_eq_entry(xkb_compose_table_iterator_next(iter),
  756. XKB_KEY_NoSymbol, "aac",
  757. XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_c, XKB_KEY_NoSymbol);
  758. test_eq_entry(xkb_compose_table_iterator_next(iter),
  759. XKB_KEY_ae, "æ",
  760. XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_e, XKB_KEY_NoSymbol);
  761. test_eq_entry(xkb_compose_table_iterator_next(iter),
  762. XKB_KEY_oe, "",
  763. XKB_KEY_Multi_key, XKB_KEY_o, XKB_KEY_e, XKB_KEY_NoSymbol);
  764. test_eq_entry(xkb_compose_table_iterator_next(iter),
  765. XKB_KEY_NoSymbol, "foobar",
  766. XKB_KEY_Ahook, XKB_KEY_x, XKB_KEY_NoSymbol);
  767. assert (xkb_compose_table_iterator_next(iter) == NULL);
  768. xkb_compose_table_iterator_free(iter);
  769. xkb_compose_table_unref(table);
  770. /* QuickCheck: shuffle compose file lines and compare against
  771. * reference implementation */
  772. char *input = test_read_file("locale/en_US.UTF-8/Compose");
  773. assert(input);
  774. struct text_line lines[6000];
  775. size_t input_length = strlen(input);
  776. size_t lines_count = split_lines(input, input_length, lines, ARRAY_SIZE(lines));
  777. /* Note: we may add additional new line char */
  778. char *shuffled = calloc(input_length + 1, sizeof(char));
  779. assert(shuffled);
  780. for (size_t k = 0; k < quickcheck_loops; k++) {
  781. size_t shuffled_length = shuffle_lines(lines, lines_count, shuffled);
  782. table = xkb_compose_table_new_from_buffer(ctx, shuffled, shuffled_length, "",
  783. XKB_COMPOSE_FORMAT_TEXT_V1,
  784. XKB_COMPOSE_COMPILE_NO_FLAGS);
  785. assert(table);
  786. iter = xkb_compose_table_iterator_new(table);
  787. assert(iter);
  788. xkb_compose_table_for_each(table, compose_traverse_fn, iter);
  789. assert(xkb_compose_table_iterator_next(iter) == NULL);
  790. xkb_compose_table_iterator_free(iter);
  791. xkb_compose_table_unref(table);
  792. }
  793. free(shuffled);
  794. free(input);
  795. }
  796. static void
  797. test_string_length(struct xkb_context *ctx)
  798. {
  799. // Invalid: empty string
  800. const char table_string_1[] = "<a> <b> : \"\" X\n";
  801. assert(test_compose_seq_buffer(ctx, table_string_1,
  802. XKB_KEY_a, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  803. XKB_KEY_b, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "", XKB_KEY_X,
  804. XKB_KEY_NoSymbol));
  805. char long_string[XKB_COMPOSE_MAX_STRING_SIZE] = { 0 };
  806. memset(long_string, 0x61, XKB_COMPOSE_MAX_STRING_SIZE - 1);
  807. char table_string_2[XKB_COMPOSE_MAX_STRING_SIZE + sizeof(table_string_1) - 1];
  808. assert(snprintf_safe(table_string_2, sizeof(table_string_2),
  809. "<a> <b> : \"%s\" X\n", long_string));
  810. assert(test_compose_seq_buffer(ctx, table_string_2,
  811. XKB_KEY_a, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  812. XKB_KEY_b, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, long_string, XKB_KEY_X,
  813. XKB_KEY_NoSymbol));
  814. }
  815. static void
  816. test_decode_escape_sequences(struct xkb_context *ctx)
  817. {
  818. /* The following escape sequences should be ignored:
  819. * • \401 overflows
  820. * • \0 and \x0 produce NULL
  821. */
  822. const char table_string_1[] = "<o> <e> : \"\\401f\\x0o\\0o\" X\n";
  823. assert(test_compose_seq_buffer(ctx, table_string_1,
  824. XKB_KEY_o, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol,
  825. XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X,
  826. XKB_KEY_NoSymbol));
  827. /* Test various cases */
  828. const char table_string_2[] =
  829. "<a> : \"\\x0abcg\\\"x\" A\n" /* hexadecimal sequence has max 2 chars */
  830. "<b> : \"éxyz\" B\n" /* non-ASCII (2 bytes) */
  831. "<c> : \"€xyz\" C\n" /* non-ASCII (3 bytes) */
  832. "<d> : \"✨xyz\" D\n" /* non-ASCII (4 bytes) */
  833. "<e> : \"✨\\x0aé\\x0a€x\\\"\" E\n"
  834. "<f> : \"\" F\n";
  835. assert(test_compose_seq_buffer(ctx, table_string_2,
  836. XKB_KEY_a, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "\x0a""bcg\"x", XKB_KEY_A,
  837. XKB_KEY_b, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "éxyz", XKB_KEY_B,
  838. XKB_KEY_c, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "€xyz", XKB_KEY_C,
  839. XKB_KEY_d, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "✨xyz", XKB_KEY_D,
  840. XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "✨\x0aé\x0a€x\"", XKB_KEY_E,
  841. XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "", XKB_KEY_F,
  842. XKB_KEY_NoSymbol));
  843. }
  844. static uint32_t
  845. random_non_null_unicode_char(bool ascii)
  846. {
  847. if (ascii)
  848. return 0x01 + (random() % 0x80);
  849. switch (random() % 5) {
  850. case 0:
  851. /* U+0080..U+07FF: 2 bytes in UTF-8 */
  852. return 0x80 + (random() % 0x800);
  853. case 1:
  854. /* U+0800..U+FFFF: 3 bytes in UTF-8 */
  855. return 0x800 + (random() % 0x10000);
  856. case 2:
  857. /* U+10000..U+10FFFF: 4 bytes in UTF-8 */
  858. return 0x10000 + (random() % 0x110000);
  859. default:
  860. /* NOTE: Higher probability for ASCII */
  861. /* U+0001..U+007F: 1 byte in UTF-8 */
  862. return 0x01 + (random() % 0x80);
  863. }
  864. }
  865. static void
  866. test_encode_escape_sequences(struct xkb_context *ctx)
  867. {
  868. char *escaped;
  869. /* Test empty string */
  870. escaped = escape_utf8_string_literal("");
  871. assert_streq_not_null("Empty string", "", escaped);
  872. free(escaped);
  873. /* Test specific ASCII characters: ", \ */
  874. escaped = escape_utf8_string_literal("\"\\");
  875. assert_streq_not_null("Quote and backslash", "\\\"\\\\", escaped);
  876. free(escaped);
  877. /* Test round-trip of random strings */
  878. # define SAMPLE_SIZE 1000
  879. # define MIN_CODE_POINT 0x0001
  880. # define MAX_CODE_POINTS_COUNT 15
  881. char buf[1 + MAX_CODE_POINTS_COUNT * 4];
  882. for (int ascii = 1; ascii >= 0; ascii--) {
  883. for (size_t s = 0; s < SAMPLE_SIZE; s++) {
  884. memset(buf, 0xab, sizeof(buf));
  885. /* Create the string */
  886. size_t length = 1 + (random() % MAX_CODE_POINTS_COUNT);
  887. size_t c = 0;
  888. for (size_t idx = 0; idx < length; idx++) {
  889. uint8_t nbytes = 0;
  890. /* Get a random Unicode code point and encode it in UTF-8 */
  891. do {
  892. const uint32_t cp = random_non_null_unicode_char(ascii);
  893. nbytes = utf32_to_utf8(cp, &buf[c]);
  894. } while (!nbytes); /* Handle invalid code point in UTF-8 */
  895. c += (size_t)nbytes - 1;
  896. assert(c <= sizeof(buf) - 1);
  897. }
  898. assert_printf(buf[c] == '\0', "NULL-terminated string\n");
  899. assert_printf(strlen(buf) == c, "Contains no NULL char\n");
  900. assert_printf(is_valid_utf8(buf, c),
  901. "Invalid input UTF-8 string: \"%s\"\n", buf);
  902. /* Escape the string */
  903. escaped = escape_utf8_string_literal(buf);
  904. if (!escaped)
  905. break;
  906. assert_printf(is_valid_utf8(escaped, strlen(escaped)),
  907. "Invalid input UTF-8 string: %s\n", escaped);
  908. char *string_literal = asprintf_safe("\"%s\"", escaped);
  909. if (!string_literal) {
  910. free(escaped);
  911. break;
  912. }
  913. /* Unescape the string */
  914. char *unescaped = parse_string_literal(ctx, string_literal);
  915. assert_streq_not_null("Escaped string", buf, unescaped);
  916. free(unescaped);
  917. free(string_literal);
  918. free(escaped);
  919. }
  920. }
  921. # undef SAMPLE_SIZE
  922. # undef MIN_CODE_POINT
  923. # undef MAX_CODE_POINTS_COUNT
  924. }
  925. /* Roundtrip check: check that a table parsed from a file and the table parsed
  926. * from the dump of the previous table are identical */
  927. static void
  928. test_roundtrip(struct xkb_context *ctx)
  929. {
  930. /* TODO: add support for systems without open_memstream */
  931. #if HAVE_OPEN_MEMSTREAM
  932. bool ok = false;
  933. /* Parse reference file */
  934. char *input = test_read_file("locale/en_US.UTF-8/Compose");
  935. assert(input);
  936. size_t input_length = strlen(input);
  937. struct xkb_compose_table *ref_table = xkb_compose_table_new_from_buffer(
  938. ctx, input, input_length, "",
  939. XKB_COMPOSE_FORMAT_TEXT_V1,
  940. XKB_COMPOSE_COMPILE_NO_FLAGS
  941. );
  942. free(input);
  943. assert(ref_table);
  944. /* Dump reference Compose table */
  945. char *output;
  946. size_t output_length = 0;
  947. FILE *output_file = open_memstream(&output, &output_length);
  948. assert(output_file);
  949. ok = xkb_compose_table_dump(output_file, ref_table);
  950. fclose(output_file);
  951. if (!ok) {
  952. free(output);
  953. xkb_compose_table_unref(ref_table);
  954. exit(TEST_SETUP_FAILURE);
  955. }
  956. /* Parse dumped table */
  957. struct xkb_compose_table *table = xkb_compose_table_new_from_buffer(
  958. ctx, output, output_length, "",
  959. XKB_COMPOSE_FORMAT_TEXT_V1,
  960. XKB_COMPOSE_COMPILE_NO_FLAGS
  961. );
  962. free(output);
  963. assert(table);
  964. /* Check roundtrip by comparing table entries */
  965. struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(table);
  966. xkb_compose_table_for_each(ref_table, compose_traverse_fn, iter);
  967. assert(xkb_compose_table_iterator_next(iter) == NULL);
  968. xkb_compose_table_iterator_free(iter);
  969. xkb_compose_table_unref(table);
  970. xkb_compose_table_unref(ref_table);
  971. #endif
  972. }
  973. /* CLI positional arguments:
  974. * 1. Seed for the pseudo-random generator:
  975. * - Leave it unset or set it to “-” to use current time.
  976. * - Use an integer to set it explicitly.
  977. * 2. Number of quickcheck loops:
  978. * - Leave it unset to use the default. It depends if the `RUNNING_VALGRIND`
  979. * environment variable is set.
  980. * - Use an integer to set it explicitly.
  981. */
  982. int
  983. main(int argc, char *argv[])
  984. {
  985. struct xkb_context *ctx;
  986. test_init();
  987. ctx = test_get_context(CONTEXT_NO_FLAG);
  988. assert(ctx);
  989. /* Initialize pseudo-random generator with program arg or current time */
  990. unsigned int seed;
  991. if (argc >= 2 && !streq(argv[1], "-")) {
  992. char *endp = argv[1];
  993. errno = 0;
  994. const unsigned long raw = strtoul(argv[1], &endp, 10);
  995. if (errno || endp == argv[1] || *endp != '\0' || raw > UINT_MAX) {
  996. fprintf(stderr, "ERROR: Invalid seed: \"%s\"\n", argv[1]);
  997. exit(TEST_SETUP_FAILURE);
  998. }
  999. seed = (unsigned int) raw;
  1000. } else {
  1001. seed = (unsigned int) time(NULL);
  1002. }
  1003. fprintf(stderr, "Seed for the pseudo-random generator: %u\n", seed);
  1004. srand(seed);
  1005. /* Determine number of loops for quickchecks */
  1006. size_t quickcheck_loops = 50; /* Default */
  1007. if (argc > 2) {
  1008. /* From command-line */
  1009. char *endp = argv[2];
  1010. errno = 0;
  1011. const intmax_t raw = strtoimax(argv[2], &endp, 10);
  1012. if (errno || endp == argv[2] || *endp != '\0' ||
  1013. raw < 0 || (uintmax_t)raw > SIZE_MAX) {
  1014. fprintf(stderr, "ERROR: Invalid quickcheck loops: \"%s\"\n", argv[2]);
  1015. exit(TEST_SETUP_FAILURE);
  1016. }
  1017. quickcheck_loops = (size_t)raw;
  1018. } else if (getenv("RUNNING_VALGRIND") != NULL) {
  1019. /* Reduce if running Valgrind */
  1020. quickcheck_loops = quickcheck_loops / 20;
  1021. }
  1022. /*
  1023. * Ensure no environment variables but “top_srcdir” is set. This ensures
  1024. * that user Compose file paths are unset before the tests and set
  1025. * explicitly when necessary.
  1026. */
  1027. #ifdef __linux__
  1028. const char *srcdir = getenv("top_srcdir");
  1029. clearenv();
  1030. if (srcdir)
  1031. setenv("top_srcdir", srcdir, 1);
  1032. #else
  1033. unsetenv("XCOMPOSEFILE");
  1034. unsetenv("XDG_CONFIG_HOME");
  1035. unsetenv("HOME");
  1036. unsetenv("XLOCALEDIR");
  1037. #endif
  1038. test_compose_utf8_bom(ctx);
  1039. test_invalid_encodings(ctx);
  1040. test_seqs(ctx);
  1041. test_conflicting(ctx);
  1042. test_XCOMPOSEFILE(ctx);
  1043. test_from_locale(ctx);
  1044. test_state(ctx);
  1045. test_modifier_syntax(ctx);
  1046. test_include(ctx);
  1047. test_override(ctx);
  1048. test_traverse(ctx, quickcheck_loops);
  1049. test_string_length(ctx);
  1050. test_decode_escape_sequences(ctx);
  1051. test_encode_escape_sequences(ctx);
  1052. test_roundtrip(ctx);
  1053. xkb_context_unref(ctx);
  1054. return EXIT_SUCCESS;
  1055. }