/* * Copyright © 2014 Ran Benita * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include "xkbcommon/xkbcommon-compose.h" #include "test.h" #include "src/utf8.h" #include "src/keysym.h" #include "src/compose/parser.h" #include "src/compose/dump.h" static const char * compose_status_string(enum xkb_compose_status status) { switch (status) { case XKB_COMPOSE_NOTHING: return "nothing"; case XKB_COMPOSE_COMPOSING: return "composing"; case XKB_COMPOSE_COMPOSED: return "composed"; case XKB_COMPOSE_CANCELLED: return "cancelled"; } return ""; } static const char * feed_result_string(enum xkb_compose_feed_result result) { switch (result) { case XKB_COMPOSE_FEED_IGNORED: return "ignored"; case XKB_COMPOSE_FEED_ACCEPTED: return "accepted"; } return ""; } /* * Feed a sequence of keysyms to a fresh compose state and test the outcome. * * The varargs consists of lines in the following format: * * Terminated by a line consisting only of XKB_KEY_NoSymbol. */ static bool test_compose_seq_va(struct xkb_compose_table *table, va_list ap) { int ret; struct xkb_compose_state *state; char buffer[XKB_KEYSYM_NAME_MAX_SIZE]; state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS); assert(state); for (int i = 1; ; i++) { xkb_keysym_t input_keysym; enum xkb_compose_feed_result result, expected_result; enum xkb_compose_status status, expected_status; const char *expected_string; xkb_keysym_t keysym, expected_keysym; input_keysym = va_arg(ap, xkb_keysym_t); if (input_keysym == XKB_KEY_NoSymbol) break; expected_result = va_arg(ap, enum xkb_compose_feed_result); expected_status = va_arg(ap, enum xkb_compose_status); expected_string = va_arg(ap, const char *); expected_keysym = va_arg(ap, xkb_keysym_t); result = xkb_compose_state_feed(state, input_keysym); if (result != expected_result) { fprintf(stderr, "after feeding %d keysyms:\n", i); fprintf(stderr, "expected feed result: %s\n", feed_result_string(expected_result)); fprintf(stderr, "got feed result: %s\n", feed_result_string(result)); goto fail; } status = xkb_compose_state_get_status(state); if (status != expected_status) { fprintf(stderr, "after feeding %d keysyms:\n", i); fprintf(stderr, "expected status: %s\n", compose_status_string(expected_status)); fprintf(stderr, "got status: %s\n", compose_status_string(status)); goto fail; } ret = xkb_compose_state_get_utf8(state, buffer, sizeof(buffer)); if (ret < 0 || (size_t) ret >= sizeof(buffer)) { fprintf(stderr, "after feeding %d keysyms:\n", i); fprintf(stderr, "expected string: %s\n", expected_string); fprintf(stderr, "got error: %d\n", ret); goto fail; } if (!streq(buffer, expected_string)) { fprintf(stderr, "after feeding %d keysyms:\n", i); fprintf(stderr, "expected string: %s\n", strempty(expected_string)); fprintf(stderr, "got string: %s\n", buffer); goto fail; } keysym = xkb_compose_state_get_one_sym(state); if (keysym != expected_keysym) { fprintf(stderr, "after feeding %d keysyms:\n", i); xkb_keysym_get_name(expected_keysym, buffer, sizeof(buffer)); fprintf(stderr, "expected keysym: %s\n", buffer); xkb_keysym_get_name(keysym, buffer, sizeof(buffer)); fprintf(stderr, "got keysym (%#x): %s\n", keysym, buffer); goto fail; } } xkb_compose_state_unref(state); return true; fail: xkb_compose_state_unref(state); return false; } static bool test_compose_seq(struct xkb_compose_table *table, ...) { va_list ap; bool ok; va_start(ap, table); ok = test_compose_seq_va(table, ap); va_end(ap); return ok; } static bool test_compose_seq_buffer(struct xkb_context *ctx, const char *buffer, ...) { va_list ap; bool ok; struct xkb_compose_table *table; table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); va_start(ap, buffer); ok = test_compose_seq_va(table, ap); va_end(ap); xkb_compose_table_unref(table); return ok; } static void test_compose_utf8_bom(struct xkb_context *ctx) { const char buffer[] = "\xef\xbb\xbf : X"; assert(test_compose_seq_buffer(ctx, buffer, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "X", XKB_KEY_X, XKB_KEY_NoSymbol)); } static void test_invalid_encodings(struct xkb_context *ctx) { struct xkb_compose_table *table; /* ISO 8859-1 (latin1) */ const char iso_8859_1[] = " : \"\xe1\" acute"; assert(!test_compose_seq_buffer(ctx, iso_8859_1, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "\xc3\xa1", XKB_KEY_acute, XKB_KEY_NoSymbol)); /* UTF-16LE */ const char utf_16_le[] = "<\0A\0>\0 \0:\0 \0X\0\n\0" "<\0B\0>\0 \0:\0 \0Y\0"; table = xkb_compose_table_new_from_buffer(ctx, utf_16_le, sizeof(utf_16_le), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); /* UTF-16BE */ const char utf_16_be[] = "\0<\0A\0>\0 \0:\0 \0X\0\n" "\0<\0B\0>\0 \0:\0 \0Y"; table = xkb_compose_table_new_from_buffer(ctx, utf_16_be, sizeof(utf_16_be), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); /* UTF-16BE with BOM */ const char utf_16_be_bom[] = "\xfe\xff" "\0<\0A\0>\0 \0:\0 \0X\0\n" "\0<\0B\0>\0 \0:\0 \0Y"; table = xkb_compose_table_new_from_buffer(ctx, utf_16_be_bom, sizeof(utf_16_be_bom), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); /* UTF-32LE */ const char utf_32_le[] = "<\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" "<\0\0\0B\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0Y\0\0\0"; table = xkb_compose_table_new_from_buffer(ctx, utf_32_le, sizeof(utf_32_le), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); /* UTF-32LE with BOM */ const char utf_32_le_bom[] = "\xff\xfe\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" "<\0\0\0B\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0Y\0\0\0"; table = xkb_compose_table_new_from_buffer(ctx, utf_32_le_bom, sizeof(utf_32_le_bom), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); /* UTF-32BE */ const char utf_32_be[] = "\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" "<\0\0\0B\0\0\0>\0\0\0 \0\0\0:\0\0\0 \0\0\0Y"; table = xkb_compose_table_new_from_buffer(ctx, utf_32_be, sizeof(utf_32_be), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); } static void test_seqs(struct xkb_context *ctx) { struct xkb_compose_table *table; char *path; FILE *file; path = test_get_path("locale/en_US.UTF-8/Compose"); file = fopen(path, "rb"); assert(file); free(path); table = xkb_compose_table_new_from_file(ctx, file, "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); fclose(file); assert(test_compose_seq(table, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "'", XKB_KEY_apostrophe, XKB_KEY_Caps_Lock, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSED, "'", XKB_KEY_apostrophe, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "´", XKB_KEY_acute, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_Multi_key, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_Shift_L, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_Caps_Lock, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_Control_L, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_T, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "@", XKB_KEY_at, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_7, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_a, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_b, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_NoSymbol)); assert(test_compose_seq(table, XKB_KEY_Multi_key, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_apostrophe, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_7, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol, XKB_KEY_7, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_Caps_Lock, XKB_COMPOSE_FEED_IGNORED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_NoSymbol)); xkb_compose_table_unref(table); /* Make sure one-keysym sequences work. */ assert(test_compose_seq_buffer(ctx, " : \"foo\" X \n" " : \"baz\" Y \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Y, XKB_KEY_NoSymbol)); /* No sequences at all. */ assert(test_compose_seq_buffer(ctx, "", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_Multi_key, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_NoSymbol)); /* Only keysym - string derived from keysym. */ assert(test_compose_seq_buffer(ctx, " : X \n" " : dollar \n" " : dead_acute \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "X", XKB_KEY_X, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "$", XKB_KEY_dollar, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "", XKB_KEY_dead_acute, XKB_KEY_NoSymbol)); /* Make sure a cancelling keysym doesn't start a new sequence. */ assert(test_compose_seq_buffer(ctx, " : X \n" " : Y \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol, XKB_KEY_D, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_CANCELLED, "", XKB_KEY_NoSymbol, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_D, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "Y", XKB_KEY_Y, XKB_KEY_NoSymbol)); } static void test_conflicting(struct xkb_context *ctx) { // new is prefix of old assert(test_compose_seq_buffer(ctx, " : \"foo\" A \n" " : \"bar\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A, XKB_KEY_NoSymbol)); // old is a prefix of new assert(test_compose_seq_buffer(ctx, " : \"bar\" B \n" " : \"foo\" A \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_A, XKB_KEY_NoSymbol)); // new duplicate of old assert(test_compose_seq_buffer(ctx, " : \"bar\" B \n" " : \"bar\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B, XKB_KEY_C, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_NOTHING, "", XKB_KEY_NoSymbol, XKB_KEY_NoSymbol)); // new same length as old #1 assert(test_compose_seq_buffer(ctx, " : \"foo\" A \n" " : \"bar\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_B, XKB_KEY_NoSymbol)); // new same length as old #2 assert(test_compose_seq_buffer(ctx, " : \"foo\" A \n" " : \"foo\" B \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_B, XKB_KEY_NoSymbol)); // new same length as old #3 assert(test_compose_seq_buffer(ctx, " : \"foo\" A \n" " : \"bar\" A \n", XKB_KEY_A, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_B, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_A, XKB_KEY_NoSymbol)); } static void test_state(struct xkb_context *ctx) { struct xkb_compose_table *table; struct xkb_compose_state *state; char *path; FILE *file; path = test_get_path("locale/en_US.UTF-8/Compose"); file = fopen(path, "rb"); assert(file); free(path); table = xkb_compose_table_new_from_file(ctx, file, "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); fclose(file); state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS); assert(state); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_reset(state); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_feed(state, XKB_KEY_NoSymbol); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_feed(state, XKB_KEY_Multi_key); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING); xkb_compose_state_reset(state); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_feed(state, XKB_KEY_Multi_key); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING); xkb_compose_state_feed(state, XKB_KEY_Multi_key); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_CANCELLED); xkb_compose_state_feed(state, XKB_KEY_Multi_key); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING); xkb_compose_state_feed(state, XKB_KEY_Multi_key); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_CANCELLED); xkb_compose_state_reset(state); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_feed(state, XKB_KEY_dead_acute); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING); xkb_compose_state_feed(state, XKB_KEY_A); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSED); xkb_compose_state_reset(state); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_feed(state, XKB_KEY_dead_acute); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSING); xkb_compose_state_feed(state, XKB_KEY_A); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_COMPOSED); xkb_compose_state_reset(state); xkb_compose_state_feed(state, XKB_KEY_NoSymbol); assert(xkb_compose_state_get_status(state) == XKB_COMPOSE_NOTHING); xkb_compose_state_unref(state); xkb_compose_table_unref(table); } static void test_XCOMPOSEFILE(struct xkb_context *ctx) { struct xkb_compose_table *table; char *path; path = test_get_path("locale/en_US.UTF-8/Compose"); setenv("XCOMPOSEFILE", path, 1); free(path); table = xkb_compose_table_new_from_locale(ctx, "blabla", XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); unsetenv("XCOMPOSEFILE"); assert(test_compose_seq(table, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde, XKB_KEY_NoSymbol)); xkb_compose_table_unref(table); } static void test_from_locale(struct xkb_context *ctx) { struct xkb_compose_table *table; char *path; path = test_get_path("locale"); setenv("XLOCALEDIR", path, 1); free(path); /* Direct directory name match. */ table = xkb_compose_table_new_from_locale(ctx, "en_US.UTF-8", XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); xkb_compose_table_unref(table); /* Direct locale name match. */ table = xkb_compose_table_new_from_locale(ctx, "C.UTF-8", XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); xkb_compose_table_unref(table); /* Alias. */ table = xkb_compose_table_new_from_locale(ctx, "univ.utf8", XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); xkb_compose_table_unref(table); /* Special case - C. */ table = xkb_compose_table_new_from_locale(ctx, "C", XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); xkb_compose_table_unref(table); /* Bogus - not found. */ table = xkb_compose_table_new_from_locale(ctx, "blabla", XKB_COMPOSE_COMPILE_NO_FLAGS); assert(!table); unsetenv("XLOCALEDIR"); } static void test_modifier_syntax(struct xkb_context *ctx) { const char *table_string; /* We don't do anything with the modifiers, but make sure we can parse * them. */ assert(test_compose_seq_buffer(ctx, "None : X \n" "Shift : Y \n" "Ctrl : Y \n" "Alt : Y \n" "Caps : Y \n" "Lock : Y \n" "Shift Ctrl : Y \n" "~Shift : Y \n" "~Shift Ctrl : Y \n" "Shift ~Ctrl : Y \n" "Shift ~Ctrl ~Alt : Y \n" "! Shift : Y \n" "! Ctrl : Y \n" "! Alt : Y \n" "! Caps : Y \n" "! Lock : Y \n" "! Shift Ctrl : Y \n" "! ~Shift : Y \n" "! ~Shift Ctrl : Y \n" "! Shift ~Ctrl : Y \n" "! Shift ~Ctrl ~Alt : Y \n" " ! Shift : Y \n" "None ! Shift : Y \n" "None

! Shift : Y \n", XKB_KEY_NoSymbol)); fprintf(stderr, "\n"); table_string = "! None : X \n" "! Foo : X \n" "None ! Shift : X \n" "! ! : X \n" "! ~ : X \n" "! ! : X \n" "! Ctrl ! Ctrl : X \n" " ! : X \n" " None : X \n" "None None : X \n" " : !Shift X \n"; assert(!xkb_compose_table_new_from_buffer(ctx, table_string, strlen(table_string), "C", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS)); fprintf(stderr, "\n"); } static void test_include(struct xkb_context *ctx) { char *path, *table_string; path = test_get_path("locale/en_US.UTF-8/Compose"); assert(path); /* We don't have a mechanism to change the include paths like we * have for keymaps. So we must include the full path. */ table_string = asprintf_safe(" : \"foo\" X\n" "include \"%s\"\n" " : \"bar\" Y\n", path); assert(table_string); assert(test_compose_seq_buffer(ctx, table_string, /* No conflict. */ XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_acute, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "´", XKB_KEY_acute, /* Comes before - doesn't override. */ XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_space, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "~", XKB_KEY_asciitilde, /* Comes after - does override. */ XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_tilde, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y, XKB_KEY_NoSymbol)); free(path); free(table_string); } static void test_override(struct xkb_context *ctx) { const char *table_string = " : \"foo\" X\n" " : \"bar\" Y\n" " : \"baz\" Z\n"; assert(test_compose_seq_buffer(ctx, table_string, /* Comes after - does override. */ XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "baz", XKB_KEY_Z, /* Override does not affect sibling nodes */ XKB_KEY_dead_circumflex, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "bar", XKB_KEY_Y, XKB_KEY_NoSymbol)); } static bool test_eq_entry_va(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym_ref, const char *utf8_ref, va_list ap) { assert (entry != NULL); assert (xkb_compose_table_entry_keysym(entry) == keysym_ref); const char *utf8 = xkb_compose_table_entry_utf8(entry); assert (utf8 && utf8_ref && strcmp(utf8, utf8_ref) == 0); size_t nsyms; const xkb_keysym_t *sequence = xkb_compose_table_entry_sequence(entry, &nsyms); xkb_keysym_t keysym; for (unsigned k = 0; ; k++) { keysym = va_arg(ap, xkb_keysym_t); if (keysym == XKB_KEY_NoSymbol) { return (k == nsyms - 1); } assert (k < nsyms); assert (keysym == sequence[k]); } } static bool test_eq_entry(struct xkb_compose_table_entry *entry, xkb_keysym_t keysym, const char *utf8, ...) { va_list ap; bool ok; va_start(ap, utf8); ok = test_eq_entry_va(entry, keysym, utf8, ap); va_end(ap); return ok; } static void test_traverse(struct xkb_context *ctx) { struct xkb_compose_table *table; const char *buffer = " : \"foo\" X\n" " : \"foobar\"\n" " : oe\n" " : \"bar\" Y\n" " : \"æ\" ae\n" " : \"baz\" Z\n" " : \"é\" eacute\n" " : \"aac\"\n" " : \"aab\"\n" " : \"aaa\"\n"; table = xkb_compose_table_new_from_buffer(ctx, buffer, strlen(buffer), "", XKB_COMPOSE_FORMAT_TEXT_V1, XKB_COMPOSE_COMPILE_NO_FLAGS); assert(table); struct xkb_compose_table_iterator *iter = xkb_compose_table_iterator_new(table); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_eacute, "é", XKB_KEY_dead_acute, XKB_KEY_e, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_Z, "baz", XKB_KEY_dead_circumflex, XKB_KEY_a, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_Y, "bar", XKB_KEY_dead_circumflex, XKB_KEY_e, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_X, "foo", XKB_KEY_dead_circumflex, XKB_KEY_dead_circumflex, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_NoSymbol, "aaa", XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_a, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_NoSymbol, "aab", XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_b, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_NoSymbol, "aac", XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_a, XKB_KEY_c, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_ae, "æ", XKB_KEY_Multi_key, XKB_KEY_a, XKB_KEY_e, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_oe, "", XKB_KEY_Multi_key, XKB_KEY_o, XKB_KEY_e, XKB_KEY_NoSymbol); test_eq_entry(xkb_compose_table_iterator_next(iter), XKB_KEY_NoSymbol, "foobar", XKB_KEY_Ahook, XKB_KEY_x, XKB_KEY_NoSymbol); assert (xkb_compose_table_iterator_next(iter) == NULL); xkb_compose_table_iterator_free(iter); xkb_compose_table_unref(table); } static void test_decode_escape_sequences(struct xkb_context *ctx) { /* The following escape sequences should be ignored: * • \401 overflows * • \0 and \x0 produce NULL */ const char table_string_1[] = " : \"\\401f\\x0o\\0o\" X\n"; assert(test_compose_seq_buffer(ctx, table_string_1, XKB_KEY_o, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSING, "", XKB_KEY_NoSymbol, XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "foo", XKB_KEY_X, XKB_KEY_NoSymbol)); /* Test various cases */ const char table_string_2[] = " : \"\\x0abcg\\\"x\" A\n" /* hexadecimal sequence has max 2 chars */ " : \"éxyz\" B\n" /* non-ASCII (2 bytes) */ " : \"€xyz\" C\n" /* non-ASCII (3 bytes) */ " : \"✨xyz\" D\n" /* non-ASCII (4 bytes) */ " : \"✨\\x0aé\\x0a€x\\\"\" E\n" " : \"\" F\n"; assert(test_compose_seq_buffer(ctx, table_string_2, XKB_KEY_a, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "\x0a""bcg\"x", XKB_KEY_A, XKB_KEY_b, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "éxyz", XKB_KEY_B, XKB_KEY_c, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "€xyz", XKB_KEY_C, XKB_KEY_d, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "✨xyz", XKB_KEY_D, XKB_KEY_e, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "✨\x0aé\x0a€x\"", XKB_KEY_E, XKB_KEY_f, XKB_COMPOSE_FEED_ACCEPTED, XKB_COMPOSE_COMPOSED, "", XKB_KEY_F, XKB_KEY_NoSymbol)); } static uint32_t random_non_null_unicode_char(bool ascii) { if (ascii) return 0x01 + (rand() % 0x80); switch (rand() % 5) { case 0: /* U+0080..U+07FF: 2 bytes in UTF-8 */ return 0x80 + (rand() % 0x800); case 1: /* U+0800..U+FFFF: 3 bytes in UTF-8 */ return 0x800 + (rand() % 0x10000); case 2: /* U+10000..U+10FFFF: 4 bytes in UTF-8 */ return 0x10000 + (rand() % 0x110000); default: /* NOTE: Higher probability for ASCII */ /* U+0001..U+007F: 1 byte in UTF-8 */ return 0x01 + (rand() % 0x80); } } static void test_encode_escape_sequences(struct xkb_context *ctx) { char *escaped; /* Test empty string */ escaped = escape_utf8_string_literal(""); assert_streq_not_null("Empty string", "", escaped); free(escaped); /* Test specific ASCII characters: ", \ */ escaped = escape_utf8_string_literal("\"\\"); assert_streq_not_null("Quote and backslash", "\\\"\\\\", escaped); free(escaped); /* Test round-trip of random strings */ # define SAMPLE_SIZE 1000 # define MIN_CODE_POINT 0x0001 # define MAX_CODE_POINTS_COUNT 15 char buf[1 + MAX_CODE_POINTS_COUNT * 4]; for (int ascii = 1; ascii >= 0; ascii--) { for (size_t s = 0; s < SAMPLE_SIZE; s++) { /* Create the string */ size_t length = 1 + (rand() % MAX_CODE_POINTS_COUNT); size_t c = 0; for (size_t idx = 0; idx < length; idx++) { int nbytes; /* Get a random Unicode code point and encode it in UTF-8 */ do { const uint32_t cp = random_non_null_unicode_char(ascii); nbytes = utf32_to_utf8(cp, &buf[c]); } while (!nbytes); /* Handle invalid code point in UTF-8 */ c += nbytes - 1; assert(c <= sizeof(buf) - 1); } assert_printf(buf[c] == '\0', "NULL-terminated string\n"); assert_printf(strlen(buf) == c, "Contains no NULL char\n"); assert_printf(is_valid_utf8(buf, c), "Invalid input UTF-8 string: \"%s\"\n", buf); /* Escape the string */ escaped = escape_utf8_string_literal(buf); if (!escaped) break; assert_printf(is_valid_utf8(escaped, strlen(escaped)), "Invalid input UTF-8 string: %s\n", escaped); char *string_literal = asprintf_safe("\"%s\"", escaped); if (!string_literal) { free(escaped); break; } /* Unescape the string */ char *unescaped = parse_string_literal(ctx, string_literal); assert_streq_not_null("Escaped string", buf, unescaped); free(unescaped); free(string_literal); free(escaped); } } # undef SAMPLE_SIZE # undef MIN_CODE_POINT # undef MAX_CODE_POINTS_COUNT } int main(int argc, char *argv[]) { struct xkb_context *ctx; ctx = test_get_context(CONTEXT_NO_FLAG); assert(ctx); /* Initialize pseudo-random generator with program arg or current time */ int seed; if (argc == 2) { seed = atoi(argv[1]); } else { seed = time(NULL); } fprintf(stderr, "Seed for the pseudo-random generator: %d\n", seed); srand(seed); /* * Ensure no environment variables but “top_srcdir” is set. This ensures * that user Compose file paths are unset before the tests and set * explicitly when necessary. */ #ifdef __linux__ const char *srcdir = getenv("top_srcdir"); clearenv(); setenv("top_srcdir", srcdir, 1); #else unsetenv("XCOMPOSEFILE"); unsetenv("XDG_CONFIG_HOME"); unsetenv("HOME"); unsetenv("XLOCALEDIR"); #endif test_compose_utf8_bom(ctx); test_invalid_encodings(ctx); test_seqs(ctx); test_conflicting(ctx); test_XCOMPOSEFILE(ctx); test_from_locale(ctx); test_state(ctx); test_modifier_syntax(ctx); test_include(ctx); test_override(ctx); test_traverse(ctx); test_decode_escape_sequences(ctx); test_encode_escape_sequences(ctx); xkb_context_unref(ctx); return 0; }